aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2024-04-09 12:17:03 -0400
committerSophie Zhang <sophie_zhang@brown.edu>2024-04-09 12:17:03 -0400
commitb158b7ffb564db8dc60da9b80b01b10f1ed8b7cf (patch)
treeaf35e320eb876c12c617fda2eb70ce19ef376b67 /src/client/views/collections
parenteecc7ee1d14719d510ec2975826022c565a35e5f (diff)
parent3b90916af8ffcbeaf5b8a3336009b84e19c22fa9 (diff)
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx13
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx11
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx59
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx25
-rw-r--r--src/client/views/collections/CollectionMenu.tsx16
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.scss1
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx108
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx22
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewDivider.tsx12
-rw-r--r--src/client/views/collections/CollectionPileView.tsx3
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx13
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx91
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx29
-rw-r--r--src/client/views/collections/CollectionSubView.tsx7
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx57
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx26
-rw-r--r--src/client/views/collections/TabDocView.tsx13
-rw-r--r--src/client/views/collections/TreeView.tsx35
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx58
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx178
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss39
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx108
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx22
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx62
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx1
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss158
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx91
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx45
29 files changed, 701 insertions, 606 deletions
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 7e484f3df..4e4bd43bf 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -14,7 +14,8 @@ import { DocumentView } from '../nodes/DocumentView';
import { FocusViewOptions } from '../nodes/FieldView';
import './CollectionCarousel3DView.scss';
import { CollectionSubView } from './CollectionSubView';
-const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
+const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss');
+
@observer
export class CollectionCarousel3DView extends CollectionSubView() {
@computed get scrollSpeed() {
@@ -38,6 +39,10 @@ export class CollectionCarousel3DView extends CollectionSubView() {
}
};
+ @computed get carouselItems() {
+ return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK);
+ }
+
centerScale = Number(CAROUSEL3D_CENTER_SCALE);
panelWidth = () => this._props.PanelWidth() / 3;
panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE);
@@ -85,7 +90,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
);
};
- return this.childLayoutPairs.map((childPair, index) => {
+ return this.carouselItems.map((childPair, index) => {
return (
<div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
{displayDoc(childPair)}
@@ -96,7 +101,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
changeSlide = (direction: number) => {
SelectionManager.DeselectAll();
- this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length;
+ this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.carouselItems.length) % this.carouselItems.length;
};
onArrowClick = (direction: number) => {
@@ -154,7 +159,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
}
@computed get dots() {
- return this.childLayoutPairs.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />);
+ return this.carouselItems.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />);
}
@computed get translateX() {
const index = NumCast(this.layoutDoc._carousel_index);
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index dae16bafb..9c370bfbb 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -12,6 +12,7 @@ import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import './CollectionCarouselView.scss';
import { CollectionSubView } from './CollectionSubView';
+import { DocumentType } from '../../documents/DocumentTypes';
@observer
export class CollectionCarouselView extends CollectionSubView() {
@@ -33,13 +34,17 @@ export class CollectionCarouselView extends CollectionSubView() {
}
};
+ @computed get carouselItems() {
+ return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK);
+ }
+
advance = (e: React.MouseEvent) => {
e.stopPropagation();
- this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length;
+ this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.carouselItems.length;
};
goback = (e: React.MouseEvent) => {
e.stopPropagation();
- this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
+ this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.carouselItems.length) % this.carouselItems.length;
};
captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => {
// first look for properties on the document in the carousel, then fallback to properties on the container
@@ -55,7 +60,7 @@ export class CollectionCarouselView extends CollectionSubView() {
captionWidth = () => this._props.PanelWidth() - 2 * this.marginX;
@computed get content() {
const index = NumCast(this.layoutDoc._carousel_index);
- const curDoc = this.childLayoutPairs?.[index];
+ const curDoc = this.carouselItems?.[index];
const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined };
const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption);
return !(curDoc?.layout instanceof Doc) ? null : (
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 31ca86f0f..b2897a9b7 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -3,15 +3,15 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import * as GoldenLayout from '../../../client/goldenLayout';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
-import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, incrementTitleCopy } from '../../../Utils';
+import { GetEffectiveAcl, inheritParentAcls, SetPropSetterCb } from '../../../fields/util';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, emptyFunction, incrementTitleCopy } from '../../../Utils';
import { DocServer } from '../../DocServer';
import { Docs } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
@@ -32,6 +32,7 @@ import './CollectionDockingView.scss';
import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView } from './CollectionSubView';
import { TabDocView } from './TabDocView';
+import { ComputedField } from '../../../fields/ScriptField';
const _global = (window /* browser */ || global) /* node */ as any;
@observer
@@ -313,8 +314,26 @@ export class CollectionDockingView extends CollectionSubView() {
}
};
+ /**
+ * This publishes Docs having titles starting with '@' to Doc.myPublishedDocs
+ * Once published, any text that uses the 'title' in its body will automatically
+ * be linked to this published document.
+ * @param target
+ * @param title
+ */
+ titleChanged = (target: any, value: any) => {
+ const title = Field.toString(value);
+ if (title.startsWith('@') && !title.substring(1).match(/[\(\)\[\]@]/) && title.length > 1) {
+ const embedding = DocListCast(target.proto_embeddings).lastElement();
+ embedding && Doc.AddToMyPublished(embedding);
+ } else if (!title.startsWith('@')) {
+ DocListCast(target.proto_embeddings).forEach(doc => Doc.RemFromMyPublished(doc));
+ }
+ };
+
componentDidMount: () => void = async () => {
this._unmounting = false;
+ SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property
if (this._containerRef.current) {
this._lightboxReactionDisposer = reaction(
() => LightboxView.LightboxDoc,
@@ -421,7 +440,6 @@ export class CollectionDockingView extends CollectionSubView() {
if (map?.DashDoc && DocumentManager.Instance.getFirstDocumentView(map.DashDoc)) {
SelectionManager.SelectView(DocumentManager.Instance.getFirstDocumentView(map.DashDoc), false);
}
- if (!className.includes('lm_close')) DocServer.UPDATE_SERVER_CACHE();
}
}
}
@@ -433,8 +451,8 @@ export class CollectionDockingView extends CollectionSubView() {
public CaptureThumbnail() {
const content = this.DocumentView?.()?.ContentDiv;
if (content) {
- const _width = Number(getComputedStyle(content).width.replace('px', ''));
- const _height = Number(getComputedStyle(content).height.replace('px', ''));
+ const _width = DivWidth(content);
+ const _height = DivHeight(content);
return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', (iconFile, _nativeWidth, _nativeHeight) => {
const proto = this.dataDoc; // Cast(img.proto, Doc, null)!;
proto['thumb_nativeWidth'] = _width;
@@ -469,10 +487,15 @@ export class CollectionDockingView extends CollectionSubView() {
json = json.replace(origtab[Id], newtab[Id]);
return newtab;
});
- const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) });
- DashboardView.SetupDashboardTrails(copy);
- DashboardView.SetupDashboardCalendars(copy); // Zaul TODO: needed?
- return DashboardView.openDashboard(copy);
+ const dashboardDoc = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) });
+
+ dashboardDoc.pane_count = 1;
+ dashboardDoc.myOverlayDocs = new List<Doc>();
+ dashboardDoc.myPublishedDocs = new List<Doc>();
+
+ DashboardView.SetupDashboardTrails(dashboardDoc);
+ DashboardView.SetupDashboardCalendars(dashboardDoc); // Zaul TODO: needed?
+ return DashboardView.openDashboard(dashboardDoc);
}
@action
@@ -489,8 +512,10 @@ export class CollectionDockingView extends CollectionSubView() {
Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc, undefined, undefined, true);
// if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed
if (tab.DashDoc.embedContainer === this.Document) tab.DashDoc.embedContainer = undefined;
- if (!tab.DashDoc.embedContainer) Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
- Doc.RemoveEmbedding(tab.DashDoc, tab.DashDoc);
+ if (!tab.DashDoc.embedContainer) {
+ Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
+ Doc.RemoveEmbedding(tab.DashDoc, tab.DashDoc);
+ }
}
if (CollectionDockingView.Instance) {
const dview = CollectionDockingView.Instance.Document;
@@ -511,13 +536,13 @@ export class CollectionDockingView extends CollectionSubView() {
stack.header?.element.on('mousedown', (e: any) => {
const dashboard = Doc.ActiveDashboard;
if (dashboard && e.target === stack.header?.element[0] && e.button === 2) {
- dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
+ dashboard.pane_count = NumCast(dashboard.pane_count) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
_width: this._props.PanelWidth(),
_height: this._props.PanelHeight(),
_freeform_backgroundGrid: true,
_layout_fitWidth: true,
- title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
+ title: `Untitled Tab ${NumCast(dashboard.pane_count)}`,
});
Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
inheritParentAcls(this.Document, docToAdd, false);
@@ -529,13 +554,13 @@ export class CollectionDockingView extends CollectionSubView() {
action(() => {
const dashboard = Doc.ActiveDashboard;
if (dashboard) {
- dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
+ dashboard.pane_count = NumCast(dashboard.pane_count) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
_width: this._props.PanelWidth(),
_height: this._props.PanelHeight(),
_layout_fitWidth: true,
_freeform_backgroundGrid: true,
- title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
+ title: `Untitled Tab ${NumCast(dashboard.pane_count)}`,
});
Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
inheritParentAcls(this.dataDoc, docToAdd, false);
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 41c5d5b42..7dcfd32bd 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -33,8 +33,7 @@ interface CMVFieldRowProps {
createDropTarget: (ele: HTMLDivElement) => void;
screenToLocalTransform: () => Transform;
setDocHeight: (key: string, thisHeight: number) => void;
- observeHeight: (myref: any) => void;
- unobserveHeight: (myref: any) => void;
+ refList: any[];
showHandle: boolean;
}
@@ -68,20 +67,20 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
createRowDropRef = (ele: HTMLDivElement | null) => {
this._dropDisposer?.();
- if (ele) {
- this._ele = ele;
- this._props.observeHeight(ele);
- this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Document);
- }
+ if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Document);
+ else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
+ this._ele = ele;
};
@action
componentDidMount() {
this.heading = this._props.headingObject?.heading || '';
this.color = this._props.headingObject?.color || '#f1efeb';
this.collapsed = this._props.headingObject?.collapsed || false;
+ this._ele && this.props.refList.push(this._ele);
}
componentWillUnmount() {
- this._props.unobserveHeight(this._ele);
+ this._ele && this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
+ this._ele = null;
}
getTrueHeight = () => {
@@ -101,9 +100,8 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
if (de.complete.docDragData) {
const key = this._props.pivotField;
const castedValue = this.getValue(this.heading);
- const onLayoutDoc = this.onLayoutDoc(key);
if (this._props.parent.onInternalDrop(e, de)) {
- de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc));
+ key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !this.onLayoutDoc(key)));
}
return true;
}
@@ -129,7 +127,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
return false;
}
}
- this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
+ key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
this._heading = castedValue.toString();
return true;
}
@@ -156,10 +154,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
this._createEmbeddingSelected = false;
const key = this._props.pivotField;
const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
- const onLayoutDoc = this.onLayoutDoc(key);
FormattedTextBox.SetSelectOnLoad(newDoc);
FormattedTextBox.SelectOnLoadChar = value;
- (onLayoutDoc ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading);
+ key && ((this.onLayoutDoc(key) ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading));
const docs = this._props.parent.childDocList;
return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
};
@@ -168,7 +165,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
action(() => {
this._createEmbeddingSelected = false;
const key = this._props.pivotField;
- this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
+ key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
if (this._props.parent.colHeaderData && this._props.headingObject) {
const index = this._props.parent.colHeaderData.indexOf(this._props.headingObject);
this._props.parent.colHeaderData.splice(index, 1);
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 0f90818ef..4f25f69ef 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,17 +1,18 @@
-import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, Lambda, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { Lambda, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { RichTextField } from '../../../fields/RichTextField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DragManager } from '../../util/DragManager';
+import { DragManager, dropActionType } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
import { Transform } from '../../util/Transform';
@@ -19,11 +20,10 @@ import { undoBatch } from '../../util/UndoManager';
import { AntimodeMenu } from '../AntimodeMenu';
import { EditableView } from '../EditableView';
import { MainView } from '../MainView';
-import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
import { DefaultStyleProvider } from '../StyleProvider';
-import { CollectionLinearView } from './collectionLinear';
+import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
import './CollectionMenu.scss';
-import { DocData } from '../../../fields/DocSymbols';
+import { CollectionLinearView } from './collectionLinear';
interface CollectionMenuProps {
panelHeight: () => number;
@@ -91,7 +91,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
Document={selDoc}
docViewPath={returnEmptyDocViewList}
fieldKey="data"
- dropAction="embed"
+ dropAction={dropActionType.embed}
styleProvider={DefaultStyleProvider}
select={emptyFunction}
isContentActive={returnTrue}
diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss
index 91a82d40f..4c2dcf9ab 100644
--- a/src/client/views/collections/CollectionNoteTakingView.scss
+++ b/src/client/views/collections/CollectionNoteTakingView.scss
@@ -98,6 +98,7 @@
overflow: auto;
display: flex;
flex-direction: column;
+ height: max-content;
}
.collectionSchemaView-previewDoc {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index b8133806f..5b91216cb 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -9,7 +9,7 @@ import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnFalse, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { DivHeight, emptyFunction, returnZero, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -19,14 +19,13 @@ import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { LightboxView } from '../LightboxView';
import { DocumentView } from '../nodes/DocumentView';
-import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
+import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { StyleProp } from '../StyleProvider';
import './CollectionNoteTakingView.scss';
import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn';
import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider';
import { CollectionSubView } from './CollectionSubView';
-import { JsxElement } from 'typescript';
const _global = (window /* browser */ || global) /* node */ as any;
/**
@@ -45,6 +44,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
public DividerWidth = 16;
@observable docsDraggedRowCol: number[] = [];
@observable _scroll = 0;
+ @observable _refList: any[] = [];
constructor(props: any) {
super(props);
@@ -157,8 +157,16 @@ export class CollectionNoteTakingView extends CollectionSubView() {
document.addEventListener('pointerup', this.removeDocDragHighlight, true);
this._disposers.layout_autoHeight = reaction(
() => this.layoutDoc._layout_autoHeight,
- layout_autoHeight =>
- layout_autoHeight && this._props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', ''))))))
+ layout_autoHeight => layout_autoHeight && this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight)))
+ );
+
+ this._disposers.refList = reaction(
+ () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }),
+ ({ refList, autoHeight }) => {
+ if (autoHeight) refList.forEach(r => this.observer.observe(r));
+ else this.observer.disconnect();
+ },
+ { fireImmediately: true }
);
}
@@ -345,7 +353,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// get the index for where you need to insert the doc you are currently dragging
const clientY = this.ScreenToLocalBoxXf().transformPoint(ex, ey)[1];
let dropInd = -1;
- let pos0 = (this.refList.lastElement() as HTMLDivElement).children[0].getBoundingClientRect().height + this.yMargin * 2;
+ let pos0 = (this._refList.lastElement() as HTMLDivElement).children[0].getBoundingClientRect().height + this.yMargin * 2;
colDocs.forEach((doc, i) => {
let pos1 = this.getDocHeight(doc) + 2 * this.gridGap;
pos1 += pos0;
@@ -424,7 +432,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
if (super.onInternalDrop(e, de)) {
// filter out the currently dragged docs from the child docs, since we will insert them later
const rowCol = this.docsDraggedRowCol;
- const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= this.childDocs.length); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note).
+ const droppedDocs = this.childDocs.filter((d: Doc, ind: number) => ind >= this.childDocs.length); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note).
const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments;
const docs = this.childDocList;
if (docs && newDocs.length) {
@@ -489,65 +497,45 @@ export class CollectionNoteTakingView extends CollectionSubView() {
headings = () => Array.from(this.Sections);
- refList: any[] = [];
-
editableViewProps = () => ({
GetValue: () => '',
SetValue: this.addGroup,
contents: '+ New Column',
});
+ refList = () => this._refList;
+
// sectionNoteTaking returns a CollectionNoteTakingViewColumn (which is an individual column)
- sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
- const type = 'number';
- return (
- <CollectionNoteTakingViewColumn
- key={heading?.heading ?? 'unset'}
- unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
- observeHeight={ref => {
- if (ref) {
- this.refList.push(ref);
- this.observer = new _global.ResizeObserver(
- action((entries: any) => {
- if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
- const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
- if (!LightboxView.Contains(this.DocumentView?.())) {
- this._props.setHeight?.(height);
- }
- }
- })
- );
- this.observer.observe(ref);
- }
- }}
- PanelWidth={this._props.PanelWidth}
- select={this._props.select}
- addDocument={this.addDocument}
- chromeHidden={this.chromeHidden}
- colHeaderData={this.colHeaderData}
- Document={this.Document}
- TemplateDataDocument={this._props.TemplateDataDocument}
- resizeColumns={this.resizeColumns}
- renderChildren={this.children}
- numGroupColumns={this.numGroupColumns}
- gridGap={this.gridGap}
- pivotField={this.notetakingCategoryField}
- fieldKey={this.fieldKey}
- dividerWidth={this.DividerWidth}
- maxColWidth={this.maxColWidth}
- availableWidth={this.availableWidth}
- headings={this.headings}
- heading={heading?.heading ?? 'unset'}
- headingObject={heading}
- docList={docList}
- yMargin={this.yMargin}
- type={type}
- createDropTarget={this.createDashEventsTarget}
- screenToLocalTransform={this.ScreenToLocalBoxXf}
- editableViewProps={this.editableViewProps}
- />
- );
- };
+ sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => (
+ <CollectionNoteTakingViewColumn
+ key={heading?.heading ?? 'unset'}
+ PanelWidth={this._props.PanelWidth}
+ refList={this._refList}
+ select={this._props.select}
+ addDocument={this.addDocument}
+ chromeHidden={this.chromeHidden}
+ colHeaderData={this.colHeaderData}
+ Document={this.Document}
+ TemplateDataDocument={this._props.TemplateDataDocument}
+ resizeColumns={this.resizeColumns}
+ renderChildren={this.children}
+ numGroupColumns={this.numGroupColumns}
+ gridGap={this.gridGap}
+ pivotField={this.notetakingCategoryField}
+ fieldKey={this.fieldKey}
+ dividerWidth={this.DividerWidth}
+ maxColWidth={this.maxColWidth}
+ availableWidth={this.availableWidth}
+ headings={this.headings}
+ heading={heading?.heading ?? 'unset'}
+ headingObject={heading}
+ docList={docList}
+ yMargin={this.yMargin}
+ createDropTarget={this.createDashEventsTarget}
+ screenToLocalTransform={this.ScreenToLocalBoxXf}
+ editableViewProps={this.editableViewProps}
+ />
+ );
// addGroup is called when adding a new columnHeader, adding a SchemaHeaderField to our list of
// columnHeaders and resizing the existing columns to make room for our new one.
@@ -619,7 +607,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
return this.isContentActive() === false ? 'none' : undefined;
}
- observer: any;
+ observer = new _global.ResizeObserver(() => this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight))));
render() {
TraceMobx();
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 38846c79d..db178d500 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -36,15 +36,13 @@ interface CSVFieldColumnProps {
yMargin: number;
numGroupColumns: number;
gridGap: number;
- type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
headings: () => object[];
select: (ctrlPressed: boolean) => void;
renderChildren: (docs: Doc[]) => JSX.Element[];
addDocument: (doc: Doc | Doc[]) => boolean;
createDropTarget: (ele: HTMLDivElement) => void;
screenToLocalTransform: () => Transform;
- observeHeight: (myref: any) => void;
- unobserveHeight: (myref: any) => void;
+ refList: any[];
editableViewProps: () => any;
resizeColumns: (headers: SchemaHeaderField[]) => boolean;
maxColWidth: number;
@@ -78,15 +76,18 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
createColumnDropRef = (ele: HTMLDivElement | null) => {
this.dropDisposer?.();
- if (ele) {
- this._ele = ele;
- this._props.observeHeight(ele);
- this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
- }
+ if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
+ else if (this._ele) this.props.refList.slice(this.props.refList.indexOf(this._ele), 1);
+ this._ele = ele;
};
+ componentDidMount(): void {
+ this._ele && this.props.refList.push(this._ele);
+ }
+
componentWillUnmount() {
- this._props.unobserveHeight(this._ele);
+ this._ele && this.props.refList.splice(this._props.refList.indexOf(this._ele), 1);
+ this._ele = null;
}
@undoBatch
@@ -289,11 +290,10 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
render() {
TraceMobx();
- const heading = this._heading;
return (
<div
className="collectionNoteTakingViewFieldColumn"
- key={heading}
+ key={this._heading}
style={{
width: this.columnWidth,
background: this._background,
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index 5e4bce19d..50a97b978 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -59,6 +59,18 @@ export class CollectionNoteTakingViewDivider extends ObservableReactComponent<Di
width: 12,
borderRight: '4px solid #282828',
borderLeft: '4px solid #282828',
+ position: 'fixed',
+ pointerEvents: 'none',
+ }}
+ />
+ <div
+ className="columnResizer-handler"
+ onPointerDown={e => this.registerResizing(e)}
+ style={{
+ height: '95%',
+ width: 12,
+ borderRight: '4px solid #282828',
+ borderLeft: '4px solid #282828',
}}
/>
</div>
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index d0df77cbe..9d68c621b 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -6,6 +6,7 @@ import { ScriptField } from '../../../fields/ScriptField';
import { NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
+import { dropActionType } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { OpenWhere } from '../nodes/DocumentView';
@@ -72,7 +73,7 @@ export class CollectionPileView extends CollectionSubView() {
// pile children never have their contents active, but will be document active whenever the entire pile is.
childContentsActive={returnFalse}
childDocumentsActive={this._props.isDocumentActive}
- childDragAction="move"
+ childDragAction={dropActionType.move}
childClickScript={this.toggleIcon}
/>
</div>
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 5c47d4b9e..656f850b3 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -594,6 +594,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
<StackedTimelineAnchor
{...this._props}
mark={d.anchor}
+ containerViewPath={this._props.containerViewPath}
rangeClickScript={this.rangeClickScript}
rangePlayScript={this.rangePlayScript}
left={left - this._scroll}
@@ -701,6 +702,7 @@ interface StackedTimelineAnchorProps {
layoutDoc: Doc;
isDocumentActive?: () => boolean | undefined;
ScreenToLocalTransform: () => Transform;
+ containerViewPath?: () => DocumentView[];
_timeline: HTMLDivElement | null;
focus: FocusFuncType;
currentTimecode: () => number;
@@ -796,12 +798,15 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
);
};
+ resetTitle = () => {
+ this._props.mark[DocData].title = ComputedField.MakeFunction(
+ `["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"]`
+ );
+ };
// context menu
contextMenuItems = () => {
const resetTitle = {
- script: ScriptField.MakeFunction(
- `this.title = this["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"])`
- )!,
+ method: this.resetTitle,
icon: 'folder-plus',
label: 'Reset Title',
};
@@ -826,7 +831,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
ref={action((r: DocumentView | null) => (anchor.view = r))}
Document={mark}
TemplateDataDocument={undefined}
- containerViewPath={returnEmptyDoclist}
+ containerViewPath={this._props.containerViewPath}
pointerEvents={this.noEvents ? returnNone : undefined}
styleProvider={this._props.styleProvider}
renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 54314f62c..3a62a53d7 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -11,12 +11,11 @@ import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { DivHeight, emptyFunction, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SettingsManager } from '../../util/SettingsManager';
-import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
@@ -25,7 +24,7 @@ import { EditableView } from '../EditableView';
import { LightboxView } from '../LightboxView';
import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
import { DocumentView } from '../nodes/DocumentView';
-import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
+import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { StyleProp } from '../StyleProvider';
import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
@@ -45,17 +44,16 @@ export type collectionStackingViewProps = {
@observer
export class CollectionStackingView extends CollectionSubView<Partial<collectionStackingViewProps>>() {
+ _disposers: { [key: string]: IReactionDisposer } = {};
_masonryGridRef: HTMLDivElement | null = null;
// used in a column dragger, likely due for the masonry grid view. We want to use this
_draggerRef = React.createRef<HTMLDivElement>();
- // Not sure what a pivot field is. Seems like we cause reaction in MobX get rid of it once we exit this view
- _pivotFieldDisposer?: IReactionDisposer;
- // Seems like we cause reaction in MobX get rid of our height once we exit this view
- _layout_autoHeightDisposer?: IReactionDisposer;
// keeping track of documents. Updated on internal and external drops. What's the difference?
_docXfs: { height: () => number; width: () => number; stackedDocTransform: () => Transform }[] = [];
// Doesn't look like this field is being used anywhere. Obsolete?
_columnStart: number = 0;
+
+ @observable _refList: any[] = [];
// map of node headers to their heights. Used in Masonry
@observable _heightMap = new Map<string, number>();
// Assuming that this is the current css cursor style
@@ -207,27 +205,28 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this._props.setContentViewBox?.(this);
// reset section headers when a new filter is inputted
- this._pivotFieldDisposer = reaction(
+ this._disposers.pivotField = reaction(
() => this.pivotField,
() => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List())
);
- this._layout_autoHeightDisposer = reaction(
+ this._disposers.autoHeight = reaction(
() => this.layoutDoc._layout_autoHeight,
- layout_autoHeight =>
- layout_autoHeight &&
- this._props.setHeight?.(
- Math.min(
- NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
- this.headerMargin + (this.isStackingView ? Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))) : this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0))
- )
- )
+ layout_autoHeight => layout_autoHeight && this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0)))
+ );
+ this._disposers.refList = reaction(
+ () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }),
+ ({ refList, autoHeight }) => {
+ this.observer.disconnect();
+ if (autoHeight) refList.forEach(r => this.observer.observe(r));
+ },
+ { fireImmediately: true }
);
}
componentWillUnmount() {
super.componentWillUnmount();
- this._pivotFieldDisposer?.();
- this._layout_autoHeightDisposer?.();
+ this.observer.disconnect();
+ Object.keys(this._disposers).forEach(key => this._disposers[key]());
}
isAnyChildContentActive = () => this._props.isAnyChildContentActive();
@@ -235,10 +234,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
};
- createRef = (ele: HTMLDivElement | null) => {
- this._masonryGridRef = ele;
- this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
- };
onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
@computed get onChildDoubleClickHandler() {
@@ -523,7 +518,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
});
};
headings = () => Array.from(this.Sections);
- refList: any[] = [];
// what a section looks like if we're in stacking view
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.pivotField;
@@ -536,23 +530,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
return (
<CollectionStackingViewFieldColumn
- unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
- observeHeight={ref => {
- if (ref) {
- this.refList.push(ref);
- this.observer = new _global.ResizeObserver(
- action((entries: any) => {
- if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
- const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
- if (!LightboxView.Contains(this.DocumentView?.())) {
- this._props.setHeight?.(height);
- }
- }
- })
- );
- this.observer.observe(ref);
- }
- }}
+ refList={this._refList}
addDocument={this.addDocument}
chromeHidden={this.chromeHidden}
colHeaderData={this.colHeaderData}
@@ -591,21 +569,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
Document={this.Document}
chromeHidden={this.chromeHidden}
pivotField={this.pivotField}
- unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
- observeHeight={ref => {
- if (ref) {
- this.refList.push(ref);
- this.observer = new _global.ResizeObserver(
- action((entries: any) => {
- if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
- const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0);
- this._props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel
- }
- })
- );
- this.observer.observe(ref);
- }
- }}
+ refList={this._refList}
key={heading ? heading.heading : ''}
rows={rows}
headings={this.headings}
@@ -709,7 +673,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
@computed get backgroundEvents() {
return this._props.isContentActive() === false ? 'none' : undefined;
}
- observer: any;
+
+ observer = new _global.ResizeObserver(() => this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0))));
+
+ onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
+ _oldWheel: any;
render() {
TraceMobx();
const editableViewProps = {
@@ -730,7 +698,14 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
<div className="collectionStackingMasonry-cont">
<div
className={this.isStackingView ? 'collectionStackingView' : 'collectionMasonryView'}
- ref={this.createRef}
+ ref={ele => {
+ this._masonryGridRef = ele;
+ this.createDashEventsTarget(ele); //so the whole grid is the drop target?
+ this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+ this._oldWheel = ele;
+ // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
+ ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ }}
style={{
overflowY: this.isContentActive() ? 'auto' : 'hidden',
background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor),
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index c455f20d8..641e01b81 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -9,7 +9,7 @@ import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
+import { DivHeight, DivWidth, emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
@@ -44,8 +44,7 @@ interface CSVFieldColumnProps {
addDocument: (doc: Doc | Doc[]) => boolean;
createDropTarget: (ele: HTMLDivElement) => void;
screenToLocalTransform: () => Transform;
- observeHeight: (myref: any) => void;
- unobserveHeight: (myref: any) => void;
+ refList: any[];
}
@observer
@@ -53,7 +52,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
private dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
- @observable private _background = 'inherit';
+ @observable _background = 'inherit';
@observable _paletteOn = false;
@observable _heading = '';
@observable _color = '';
@@ -71,15 +70,14 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
// is that the only way to have drop targets?
createColumnDropRef = (ele: HTMLDivElement | null) => {
this.dropDisposer?.();
- if (ele) {
- this._ele = ele;
- this._props.observeHeight(ele);
- this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
- }
+ if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
+ else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
+ this._ele = ele;
};
@action
componentDidMount() {
+ this._ele && this.props.refList.push(this._ele);
this._disposers.collapser = reaction(
() => this._props.headingObject?.collapsed,
collapsed => (this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false),
@@ -88,7 +86,8 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
}
componentWillUnmount() {
this._disposers.collapser?.();
- this._props.unobserveHeight(this._ele);
+ this._ele && this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
+ this._ele = null;
}
//TODO: what is scripting? I found it in SetInPlace def but don't know what that is
@@ -113,7 +112,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
return false;
}
- this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
+ this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
if (this._props.headingObject) {
this._props.headingObject.setHeading(castedValue.toString());
this._heading = this._props.headingObject.heading;
@@ -138,7 +137,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
if (!value && !forceEmptyNote) return false;
const key = this._props.pivotField;
const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
- newDoc[key] = this.getValue(this._props.heading);
+ key && (newDoc[key] = this.getValue(this._props.heading));
const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
newDoc.heading = heading;
@@ -149,7 +148,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
@action
deleteColumn = () => {
- this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
+ this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
if (this._props.colHeaderData && this._props.headingObject) {
const index = this._props.colHeaderData.indexOf(this._props.headingObject);
this._props.colHeaderData.splice(index, 1);
@@ -217,8 +216,8 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
const layoutItems: ContextMenuProps[] = [];
const docItems: ContextMenuProps[] = [];
const dataDoc = this._props.TemplateDataDocument || this._props.Document;
- const width = this._ele ? Number(getComputedStyle(this._ele).width.replace('px', '')) : 0;
- const height = this._ele ? Number(getComputedStyle(this._ele).height.replace('px', '')) : 0;
+ const width = this._ele ? DivWidth(this._ele) : 0;
+ const height = this._ele ? DivHeight(this._ele) : 0;
DocUtils.addDocumentCreatorMenuItems(
doc => {
FormattedTextBox.SetSelectOnLoad(doc);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index fdbd1cc90..ecc2aea31 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -15,13 +15,13 @@ import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { DocServer } from '../../DocServer';
import { Networking } from '../../Network';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { ViewBoxBaseComponent } from '../DocComponent';
import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents';
import { DragManager, dropActionType } from '../../util/DragManager';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { UndoManager, undoBatch } from '../../util/UndoManager';
-import { ViewBoxBaseComponent } from '../DocComponent';
import { LoadingBox } from '../nodes/LoadingBox';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { CollectionView, CollectionViewProps } from './CollectionView';
@@ -219,12 +219,13 @@ export function CollectionSubView<X>(moreProps?: X) {
const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => (targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop));
- if ((!dropAction || dropAction === 'inSame' || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) {
+ if ((!dropAction || dropAction === dropActionType.inPlace || dropAction === dropActionType.same || dropAction === dropActionType.move || someMoved) && docDragData.moveDocument) {
const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
if (movedDocs.length) {
const canAdd =
- (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.Document)) && (dropAction !== 'inSame' || docDragData.draggedDocuments.every(d => d.embedContainer === this.Document));
+ (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.Document)) &&
+ (dropAction !== dropActionType.inPlace || docDragData.draggedDocuments.every(d => d.embedContainer === this.Document));
const moved = docDragData.moveDocument(movedDocs, this.Document, canAdd ? this.addDocument : returnFalse);
added = canAdd || moved ? moved : undefined;
} else if (addedDocs.length) {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 38f6aa3e7..37452ddfb 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,12 +1,10 @@
-import { toUpper } from 'lodash';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
import { Doc, Opt, StrListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
-import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
@@ -15,7 +13,7 @@ import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { EditableView } from '../EditableView';
+import { FieldsDropdown } from '../FieldsDropdown';
import { DocumentView } from '../nodes/DocumentView';
import { FocusViewOptions } from '../nodes/FieldView';
import { PresBox } from '../nodes/trails';
@@ -51,7 +49,7 @@ export class CollectionTimeView extends CollectionSubView() {
}
getAnchor = (addAsAnnotation: boolean) => {
- const anchor = Docs.Create.HTMLMarkerDocument([], {
+ const anchor = Docs.Create.ConfigDocument({
title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
annotationOn: this.Document,
});
@@ -192,51 +190,6 @@ export class CollectionTimeView extends CollectionSubView() {
ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
};
- @computed get _allFacets() {
- const facets = new Set<string>();
- this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
- Doc.AreProtosEqual(this.dataDoc, this.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
- return Array.from(facets);
- }
- menuCallback = (x: number, y: number) => {
- ContextMenu.Instance.clearItems();
- const keySet: Set<string> = new Set(['tags']);
-
- this.childLayoutPairs.map(pair =>
- this._allFacets
- .filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof pair.layout[fieldKey] === 'number' || typeof pair.layout[fieldKey] === 'boolean' || typeof pair.layout[fieldKey] === 'string')
- .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0]))
- .map(fieldKey => keySet.add(fieldKey))
- );
-
- const docItems: ContextMenuProps[] = Array.from(keySet).map(fieldKey =>
- ({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); // prettier-ignore
- docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' });
- ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' });
- ContextMenu.Instance.displayMenu(x, y, ':');
- };
-
- @computed get pivotKeyUI() {
- return (
- <div className={'pivotKeyEntry'}>
- <EditableView
- GetValue={returnEmptyString}
- SetValue={(value: any) => {
- if (value?.length) {
- this.layoutDoc._pivotField = value;
- return true;
- }
- return false;
- }}
- background={'#f1efeb'} // this._props.headingObject ? this._props.headingObject.color : "#f1efeb";
- contents={':' + StrCast(this.layoutDoc._pivotField)}
- showMenuOnLoad={true}
- display={'inline'}
- menuCallback={this.menuCallback}
- />
- </div>
- );
- }
render() {
let nonNumbers = 0;
@@ -263,7 +216,6 @@ export class CollectionTimeView extends CollectionSubView() {
return (
<div className={'collectionTimeView' + (doTimeline ? '' : '-pivot')} onContextMenu={this.specificMenu} style={{ width: this._props.PanelWidth(), height: '100%' }}>
- {this.pivotKeyUI}
{this.contents}
{!this._props.isSelected() || !doTimeline ? null : (
<>
@@ -272,6 +224,9 @@ export class CollectionTimeView extends CollectionSubView() {
<div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
</>
)}
+ <div style={{ right: 0, top: 0, position: 'absolute' }}>
+ <FieldsDropdown Document={this.Document} selectFunc={fieldKey => (this.layoutDoc._pivotField = fieldKey)} placeholder={StrCast(this.layoutDoc._pivotField)} />
+ </div>
</div>
);
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 786301136..293c79119 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,10 +8,11 @@ import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero, Utils } from '../../../Utils';
+import { DivHeight, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
@@ -27,7 +28,6 @@ import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView } from './CollectionSubView';
import './CollectionTreeView.scss';
import { TreeView } from './TreeView';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
const _global = (window /* browser */ || global) /* node */ as any;
export type collectionTreeViewProps = {
@@ -113,8 +113,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
computeHeight = () => {
if (!this._isDisposing) {
- const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace('px', ''));
- const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()) + 6;
+ const titleHeight = !this._titleRef ? this.marginTop() : DivHeight(this._titleRef);
+ const bodyHeight = Array.from(this.refList).reduce((p, r) => p + DivHeight(r), this.marginBot()) + 6;
this.layoutDoc._layout_autoHeightMargins = bodyHeight;
!this._props.dontRegisterView && this._props.setHeight?.(bodyHeight + titleHeight);
}
@@ -153,12 +153,18 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return res;
}
- protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => {
+ protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => {
const dragData = de.complete.docDragData;
if (dragData) {
- const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.Document) ? true : false;
+ const sourceDragAction = dragData.dropAction;
+ const sameTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.Document);
const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d));
- dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree && dragData.dropAction !== 'inSame' ? 'same' : dragData.dropAction;
+ dragData.dropAction =
+ targetDropAction && !isAlreadyInTree() // if dropped document is not in the tree
+ ? targetDropAction // then use the target's drop action if it's specified
+ : !sameTree() || sourceDragAction === dropActionType.inPlace // if doc from another tree, or a non inPlace source drag action is specified
+ ? sourceDragAction // use the source dragAction
+ : dropActionType.same; // otherwise use same tree semantics to move within tree
e.stopPropagation();
}
};
@@ -287,7 +293,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
@observable _renderCount = 1;
@computed get treeViewElements() {
TraceMobx();
- const dragAction = StrCast(this.Document.childDragAction) as dropActionType;
+ const dragAction = StrCast(this.Document.childDragAction) as any as dropActionType;
const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false;
if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
@@ -414,7 +420,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
{!this.buttonMenu && !this.noviceExplainer ? null : (
- <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = Number(getComputedStyle(r).height.replace(/px/, ''))))}>
+ <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = DivHeight(r)))}>
{this.buttonMenu}
{this.noviceExplainer}
</div>
@@ -473,7 +479,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
isAnnotationOverlayScrollable={true}
childDocumentsActive={this._props.isContentActive}
fieldKey={this._props.fieldKey + '_annotations'}
- dropAction="move"
+ dropAction={dropActionType.move}
select={emptyFunction}
addDocument={this.addAnnotationDocument}
removeDocument={this.remAnnotationDocument}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 02aa76d82..5cb7b149b 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -30,7 +30,7 @@ import { ObservableReactComponent } from '../ObservableReactComponent';
import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
import { Colors } from '../global/globalEnums';
import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
-import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
+import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
import { KeyValueBox } from '../nodes/KeyValueBox';
import { DashFieldView } from '../nodes/formattedText/DashFieldView';
import { PinProps, PresBox, PresMovement } from '../nodes/trails';
@@ -465,6 +465,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
addDocument={undefined}
removeDocument={this.remDocTab}
addDocTab={this.addDocTab}
+ suppressSetHeight={this._document._layout_fitWidth ? true : false}
ScreenToLocalTransform={this.ScreenToLocalTransform}
dontCenter={'y'}
whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
@@ -564,6 +565,12 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
const dim = Math.max(xbounds, ybounds);
return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
}
+ @computed get xPadding() {
+ return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l));
+ }
+ @computed get yPadding() {
+ return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l));
+ }
childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
miniDown = (e: React.PointerEvent) => {
@@ -620,6 +627,8 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
fitContentsToBox={returnTrue}
+ xPadding={this.xPadding}
+ yPadding={this.yPadding}
/>
<div className="miniOverlay" onPointerDown={this.miniDown}>
<TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
@@ -630,7 +639,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
render() {
return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : (
<div className="miniMap-hidden">
- <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement={'top-end'} popup={this.popup} />
+ <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
</div>
);
}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 85f7cf7fe..8c29c3ada 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -19,7 +19,6 @@ import { DocUtils, Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
import { LinkManager } from '../../util/LinkManager';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SettingsManager } from '../../util/SettingsManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
@@ -36,7 +35,7 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
import { CollectionView } from './CollectionView';
import { TreeSort } from './TreeSort';
import './TreeView.scss';
-const { default: { TREE_BULLET_WIDTH } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
+const { TREE_BULLET_WIDTH } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
export interface TreeViewProps {
treeView: CollectionTreeView;
@@ -439,7 +438,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
droppedDocuments: Doc[],
before: boolean,
inside: number | boolean,
- dropAction: dropActionType,
+ dropAction: dropActionType | undefined,
removeDocument: DragManager.RemoveFunction | undefined,
moveDocument: DragManager.MoveFunction | undefined,
forceAdd: boolean,
@@ -449,10 +448,10 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const addDoc = inside ? this.localAdd : parentAddDoc;
const canAdd = !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add') || forceAdd;
- if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) {
- const move = (!dropAction || canEmbed || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument;
+ if (canAdd && (dropAction !== dropActionType.inPlace || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) {
+ const move = (!dropAction || canEmbed || dropAction === dropActionType.proto || dropAction === dropActionType.move || dropAction === dropActionType.same || dropAction === dropActionType.inPlace) && moveDocument;
this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = true);
- const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false);
+ const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === dropActionType.proto ? addDoc(d) : false) : addDoc(d)) || added, false);
this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = false);
return res;
}
@@ -574,11 +573,21 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
<div style={{ display: 'flex', overflow: 'auto' }} key={'newKeyValue'}>
<EditableView
key="editableView"
- contents={'+key:value'}
+ contents={'+key=value'}
height={13}
fontSize={12}
GetValue={returnEmptyString}
- SetValue={value => value.indexOf(':') !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(':')), value.substring(value.indexOf(':') + 1, value.length), true)}
+ SetValue={input => {
+ const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]+)/);
+ if (match) {
+ const key = match[1];
+ const assign = match[2];
+ const val = match[3];
+ KeyValueBox.SetField(doc, key, assign + val, false);
+ return true;
+ }
+ return false;
+ }}
/>
</div>
);
@@ -631,7 +640,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
}
return (
<div>
- {!docs?.length || this._props.AddToMap /* hack to identify pres box trees */ ? null : (
+ {!docs?.length || this.treeView.outlineMode || this._props.AddToMap /* hack to identify pres box trees */ ? null : (
<div className="treeView-sorting">
<IconButton
color={sortings[sorting]?.color}
@@ -884,7 +893,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
// prettier-ignore
switch (property.split(':')[0]) {
case StyleProp.Opacity: return this.treeView.outlineMode ? undefined : 1;
- case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
+ case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined;//StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
case StyleProp.Highlighting: if (this.treeView.outlineMode) return undefined;
case StyleProp.BoxShadow: return undefined;
case StyleProp.DocContents:
@@ -919,7 +928,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
case 'Tab':
e.stopPropagation?.();
e.preventDefault?.();
- setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
+ setTimeout(() => RichTextMenu.Instance?.TextView?.EditorView?.focus(), 150);
UndoManager.RunInBatch(() => (e.shiftKey ? this._props.outdentDocument?.(true) : this._props.indentDocument?.(true)), 'tab');
return true;
case 'Backspace':
@@ -1055,7 +1064,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
this,
e,
() => {
- this._dref?.startDragging(e.clientX, e.clientY, '' as any);
+ (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any);
return true;
},
returnFalse,
@@ -1161,7 +1170,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const before = pt[1] < rect.top + rect.height / 2;
const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
- const docs = this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false, false));
+ this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, dropActionType.copy, undefined, undefined, false, false));
};
render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 58f6b1593..73dd7fea3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
import { SettingsManager } from '../../../util/SettingsManager';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import './CollectionFreeFormView.scss';
-// import assets from './assets/link.png';
+import { Doc } from '../../../../fields/Doc';
/**
* An Fsa Arc. The first array element is a test condition function that will be observed.
@@ -15,18 +15,18 @@ import './CollectionFreeFormView.scss';
export type infoArc = [() => any, (res?: any) => infoState];
export const StateMessage = Symbol('StateMessage');
-export const StateEntryFunc = Symbol('StateEntryFunc');
export const StateMessageGIF = Symbol('StateMessageGIF');
+export const StateEntryFunc = Symbol('StateEntryFunc');
export class infoState {
[StateMessage]: string = '';
- [StateEntryFunc]?: () => any;
[StateMessageGIF]?: string = '';
+ [StateEntryFunc]?: () => any;
[key: string]: infoArc;
- constructor(message: string, arcs: { [key: string]: infoArc }, entryFunc?: () => any, messageGif?: string) {
+ constructor(message: string, arcs: { [key: string]: infoArc }, messageGif?: string, entryFunc?: () => any) {
this[StateMessage] = message;
Object.assign(this, arcs);
- this[StateEntryFunc] = entryFunc;
this[StateMessageGIF] = messageGif;
+ this[StateEntryFunc] = entryFunc;
}
}
@@ -36,16 +36,17 @@ export class infoState {
* @param arcs an object with fields containing @infoArcs (an object with field names indicating the arc transition and
* field values being a tuple of an arc transition trigger function (that returns a truthy value when the arc should fire),
* and an arc transition action function (that sets the next state)
+ * @param gif the gif displayed when in this state
* @param entryFunc a function to call when entering the state
* @returns an FSA state
*/
export function InfoState(
msg: string, //
arcs: { [key: string]: infoArc },
- entryFunc?: () => any,
- gif?: string
+ gif?: string,
+ entryFunc?: () => any
) {
- return new infoState(msg, arcs, entryFunc, gif);
+ return new infoState(msg, arcs, gif, entryFunc);
}
export interface CollectionFreeFormInfoStateProps {
@@ -57,7 +58,7 @@ export interface CollectionFreeFormInfoStateProps {
@observer
export class CollectionFreeFormInfoState extends ObservableReactComponent<CollectionFreeFormInfoStateProps> {
_disposers: IReactionDisposer[] = [];
- @observable _hide = false;
+ @observable _expanded = false;
constructor(props: any) {
super(props);
@@ -72,22 +73,16 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
}
clearState = () => this._disposers.map(disposer => disposer());
- initState = () =>
- (this._disposers = this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(arc => {
- return reaction(
- //
+ initState = () => (this._disposers =
+ this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(
+ arc => reaction(
arc.test,
- res => {
- if (res) {
- const next = arc.act(res);
- this._props.next(next);
- }
- },
+ res => res && this._props.next(arc.act(res)),
{ fireImmediately: true }
- );
- }));
+ )
+ )); // prettier-ignore
- componentDidMount(): void {
+ componentDidMount() {
this.initState();
}
componentDidUpdate(prevProps: Readonly<CollectionFreeFormInfoStateProps>) {
@@ -95,17 +90,24 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
this.clearState();
this.initState();
}
- componentWillUnmount(): void {
+ componentWillUnmount() {
this.clearState();
}
+
render() {
+ const gif = this.State?.[StateMessageGIF];
return (
- <div className="collectionFreeform-infoUI" style={{ display: this._hide ? 'none' : undefined }}>
- <div className="msg">{this.State?.[StateMessage]}</div>
- <div className="gif-container" style={{ display: this.State?.[StateMessageGIF] ? undefined : 'none' }}>
- <img className="gif" src={this.State?.[StateMessageGIF]} alt="state message gif"></img>
+ <div className={'collectionFreeform-infoUI'}>
+ <p className="collectionFreeform-infoUI-msg">
+ {this.State?.[StateMessage]}
+ <button className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')} onClick={action(() => (this._expanded = !this._expanded))}>
+ {this._expanded ? 'Less...' : 'More...'}
+ </button>
+ </p>
+ <div className={'collectionFreeform-' + (!this._expanded || !gif ? 'hidden' : 'infoUI-gif-container')}>
+ <img src={`/assets/${gif}`} alt="state message gif" />
</div>
- <div style={{ position: 'absolute', top: -10, left: -10 }}>
+ <div className="collectionFreeform-infoUI-close">
<IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(e => this.props.close())} />
</div>
</div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 8628ca3c3..cc729decc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -8,6 +8,7 @@ import { DocumentManager } from '../../../util/DocumentManager';
import { LinkManager } from '../../../util/LinkManager';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
+import { TopBar } from '../../topbar/TopBar';
import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
import { CollectionFreeFormView } from './CollectionFreeFormView';
import './CollectionFreeFormView.scss';
@@ -25,16 +26,16 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
constructor(props: any) {
super(props);
makeObservable(this);
- this.currState = this.setupStates();
+ this._currState = this.setupStates();
}
_originalbackground: string | undefined;
@observable _currState: infoState | undefined = undefined;
- get currState() { return this._currState!; } // prettier-ignore
+ get currState() { return this._currState; } // prettier-ignore
set currState(val) { runInAction(() => (this._currState = val)); } // prettier-ignore
componentWillUnmount(): void {
- this._props.Freeform.layoutDoc.backgroundColor = this._originalbackground;
+ this._props.Freeform.dataDoc.backgroundColor = this._originalbackground;
}
setCurrState = (state: infoState) => {
@@ -45,9 +46,9 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
};
setupStates = () => {
- this._originalbackground = StrCast(this._props.Freeform.layoutDoc.backgroundColor);
+ this._originalbackground = StrCast(this._props.Freeform.dataDoc.backgroundColor);
// state entry functions
- const setBackground = (colour: string) => () => (this._props.Freeform.layoutDoc.backgroundColor = colour);
+ const setBackground = (colour: string) => () => (this._props.Freeform.dataDoc.backgroundColor = colour);
const setOpacity = (opacity: number) => () => (this._props.Freeform.layoutDoc.opacity = opacity);
// arc transition trigger conditions
const firstDoc = () => (this._props.Freeform.childDocs.length ? this._props.Freeform.childDocs[0] : undefined);
@@ -60,6 +61,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
const docNewY = () => firstDoc()?.y;
const linkStart = () => DocumentLinksButton.StartLink;
+ const linkUnstart = () => !DocumentLinksButton.StartLink;
const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length;
const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView;
@@ -74,81 +76,85 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
const presentationMode = () => Doc.ActivePresentation?.presentation_status;
// set of states
- const start = InfoState('Click anywhere and begin typing to create your first text document.', {
- docCreated: [() => numDocs(), () => {
- docX = firstDoc()?.x;
- docY = firstDoc()?.y;
- return oneDoc;
- }],
- }, setBackground("blue")); // prettier-ignore
-
- const oneDoc = InfoState('Hello world! You can drag and drop to move your document around.', {
- // docCreated: [() => numDocs() > 1, () => multipleDocs],
- docDeleted: [() => numDocs() < 1, () => start],
- docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
- docX = firstDoc()?.x;
- docY = firstDoc()?.y;
- return movedDoc1;
- }],
- }, setBackground("red")); // prettier-ignore
-
- const movedDoc1 = InfoState('Great moves. Try creating a second document.', {
- docCreated: [() => numDocs() == 2, () => multipleDocs],
- docDeleted: [() => numDocs() < 1, () => start],
- docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
- docX = firstDoc()?.x;
- docY = firstDoc()?.y;
- return movedDoc2;
- }],
- }, setBackground("yellow")); // prettier-ignore
-
- const movedDoc2 = InfoState('Slick moves. Try creating a second document.', {
- docCreated: [() => numDocs() == 2, () => multipleDocs],
- docDeleted: [() => numDocs() < 1, () => start],
- docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
- docX = firstDoc()?.x;
- docY = firstDoc()?.y;
- return movedDoc3;
- }],
- }, setBackground("pink")); // prettier-ignore
-
- const movedDoc3 = InfoState('Groovy moves. Try creating a second document.', {
- docCreated: [() => numDocs() == 2, () => multipleDocs],
- docDeleted: [() => numDocs() < 1, () => start],
- docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
- docX = firstDoc()?.x;
- docY = firstDoc()?.y;
- return movedDoc1;
- }],
- }, setBackground("green")); // prettier-ignore
-
- const multipleDocs = InfoState('Let\'s create a new link. Click the link icon on one of your documents.', {
- linkStarted: [() => linkStart(), () => startedLink],
- docRemoved: [() => numDocs() < 2, () => oneDoc],
- }, setBackground("purple")); // prettier-ignore
-
- const startedLink = InfoState('Now click the highlighted link icon on your other document.', {
- linkCreated: [() => numDocLinks(), () => madeLink],
- docRemoved: [() => numDocs() < 2, () => oneDoc],
- }, setBackground("orange")); // prettier-ignore
-
- const madeLink = InfoState('You made your first link! You can view your links by selecting the blue dot.', {
- linkCreated: [() => !numDocLinks(), () => multipleDocs],
- linkViewed: [() => linkMenuOpen(), () => {
- alert(numDocLinks() + " cheer for " + numDocLinks() + " link!");
- return viewedLink;
- }],
- }, setBackground("blue")); // prettier-ignore
-
- const viewedLink = InfoState('Great work. You are now ready to create your own hypermedia world.', {
- linkDeleted: [() => !numDocLinks(), () => multipleDocs],
- docRemoved: [() => numDocs() < 2, () => oneDoc],
- docCreated: [() => numDocs() == 3, () => {
- trail = pin().length;
- return presentDocs;
- }],
- activePen: [() => activeTool() === InkTool.Pen, () => penMode],
- }, setBackground("black")); // prettier-ignore
+ const start = InfoState(
+ 'Click anywhere and begin typing to create your first text document.',
+ {
+ docCreated: [() => numDocs(), () => {
+ docX = firstDoc()?.x;
+ docY = firstDoc()?.y;
+ return oneDoc;
+ }],
+ }
+ ); // prettier-ignore
+
+ const oneDoc = InfoState(
+ 'Hello world! You can drag and drop to move your document around.',
+ {
+ // docCreated: [() => numDocs() > 1, () => multipleDocs],
+ docDeleted: [() => numDocs() < 1, () => start],
+ docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
+ docX = firstDoc()?.x;
+ docY = firstDoc()?.y;
+ return movedDoc;
+ }],
+ }
+ ); // prettier-ignore
+
+ const movedDoc = InfoState(
+ 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (\":\")',
+ {
+ docCreated: [() => numDocs() == 2, () => multipleDocs],
+ docDeleted: [() => numDocs() < 1, () => start],
+ },
+ 'dash-colon-menu.gif',
+ () => TopBar.Instance.FlipDocumentationIcon()
+ ); // prettier-ignore
+
+ const multipleDocs = InfoState(
+ 'Let\'s create a new link. Click the link icon on one of your documents.',
+ {
+ linkStarted: [() => linkStart(), () => startedLink],
+ docRemoved: [() => numDocs() < 2, () => oneDoc],
+ },
+ 'dash-create-link-board.gif'
+ ); // prettier-ignore
+
+ const startedLink = InfoState(
+ 'Now click the highlighted link icon on your other document.',
+ {
+ linkUnstart: [() => linkUnstart(), () => multipleDocs],
+ linkCreated: [() => numDocLinks(), () => madeLink],
+ docRemoved: [() => numDocs() < 2, () => oneDoc],
+ },
+ 'dash-create-link-board.gif'
+ ); // prettier-ignore
+
+ const madeLink = InfoState(
+ 'You made your first link! You can view your links by selecting the blue dot.',
+ {
+ linkCreated: [() => !numDocLinks(), () => multipleDocs],
+ linkViewed: [() => linkMenuOpen(), () => {
+ alert(numDocLinks() + " cheer for " + numDocLinks() + " link!");
+ return viewedLink;
+ }],
+ },
+ 'dash-following-link.gif'
+ ); // prettier-ignore
+
+ const viewedLink = InfoState(
+ 'Great work. You are now ready to create your own hypermedia world. Click the ? icon in the top right corner to learn more.',
+ {
+ linkDeleted: [() => !numDocLinks(), () => multipleDocs],
+ docRemoved: [() => numDocs() < 2, () => oneDoc],
+ docCreated: [() => numDocs() == 3, () => {
+ trail = pin().length;
+ return presentDocs;
+ }],
+ activePen: [() => activeTool() === InkTool.Pen, () => penMode],
+ },
+ 'documentation.png',
+ () => TopBar.Instance.FlipDocumentationIcon()
+ ); // prettier-ignore
const presentDocs = InfoState(
'Another document! You could make a presentation. Click the pin icon in the top left corner.',
@@ -162,7 +168,6 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
],
docRemoved: [() => numDocs() < 3, () => viewedLink],
},
- setBackground('black'),
'/assets/dash-pin-with-view.gif'
);
@@ -242,14 +247,17 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
docCreated: [() => numDocs() == 4, () => completed],
});
- const completed = InfoState('Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', {
- docRemoved: [() => numDocs() == 1, () => oneDoc],
- }, setBackground("white")); // prettier-ignore
+ const completed = InfoState(
+ 'Eager to learn more? Click the ? icon in the top right corner to read our full documentation.',
+ { docRemoved: [() => numDocs() == 1, () => oneDoc] },
+ 'documentation.png',
+ () => TopBar.Instance.FlipDocumentationIcon()
+ ); // prettier-ignore
return start;
};
render() {
- return <CollectionFreeFormInfoState next={this.setCurrState} close={this._props.close} infoState={this.currState} />;
+ return !this.currState ? null : <CollectionFreeFormInfoState next={this.setCurrState} close={this._props.close} infoState={this.currState} />;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index b8c0967c1..c83c26509 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -5,7 +5,6 @@ import { RefField } from '../../../../fields/RefField';
import { listSpec } from '../../../../fields/Schema';
import { Cast, NumCast, StrCast } from '../../../../fields/Types';
import { aggregateBounds } from '../../../../Utils';
-import * as React from 'react';
export interface ViewDefBounds {
type: string;
@@ -240,6 +239,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
y: -y + (pivotAxisWidth - hgt) / 2,
width: wid,
height: hgt,
+ backgroundColor: StrCast(layoutDoc.backgroundColor),
pair: { layout: doc },
replica: val.replicas[i],
});
@@ -388,7 +388,7 @@ function normalizeResults(
minWidth: number,
extras: ViewDefBounds[]
): ViewDefResult[] {
- const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height } as ViewDefBounds));
+ const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
const aggBounds = aggregateBounds(
extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: 'doc', payload: '' })))).filter(e => e.zIndex !== -99),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 9e7d364ea..2c94446fb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -273,33 +273,34 @@
margin: 15px;
padding: 10px;
+ .collectionFreeform-infoUI-close {
+ position: absolute;
+ top: -10;
+ left: -10;
+ }
- .msg {
+ .collectionFreeform-infoUI-msg {
+ position: relative;
+ max-width: 500;
+ margin: 10;
+ }
+ .collectionFreeform-infoUI-button {
+ border-radius: 50px;
+ font-size: 12px;
+ padding: 6;
position: relative;
- // display: block;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -o-user-select: none;
- user-select: none;
}
- .gif-container {
+ .collectionFreeform-infoUI-gif-container {
position: relative;
margin-top: 5px;
- // display: block;
+ pointer-events: none;
- justify-content: center;
- align-items: center;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -o-user-select: none;
- user-select: none;
-
- .gif {
- background-color: transparent;
+ > img {
height: 300px;
}
}
+ .collectionFreeform-hidden {
+ display: none;
+ }
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5cc921d85..c5102070a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -29,14 +29,14 @@ import { SelectionManager } from '../../../util/SelectionManager';
import { freeformScrollMode, SettingsManager } from '../../../util/SettingsManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
-import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
import { Timeline } from '../../animationtimeline/Timeline';
import { ContextMenu } from '../../ContextMenu';
import { GestureOverlay } from '../../GestureOverlay';
import { CtrlKey } from '../../GlobalKeyHandler';
import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
import { LightboxView } from '../../LightboxView';
-import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
+import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
@@ -80,7 +80,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
@computed get paintFunc() {
- const field = this.layoutDoc[this.fieldKey];
+ const field = this.dataDoc[this.fieldKey];
const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim();
return !paintFunc
? ''
@@ -170,8 +170,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
: aggregateBounds(
this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
- NumCast(this.layoutDoc._xPadding, 10),
- NumCast(this.layoutDoc._yPadding, 10)
+ NumCast(this.layoutDoc._xPadding, this._props.xPadding ?? 10),
+ NumCast(this.layoutDoc._yPadding, this._props.yPadding ?? 10)
);
}
@computed get nativeWidth() {
@@ -260,7 +260,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
- zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
+ zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); //, NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
PanZoomCenterXf = () =>
this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
@@ -331,8 +331,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// options.didMove = true;
// }
}
-
- if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return;
+ if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor) && !this.childLayoutPairs.map(pair => pair.layout).includes(anchor)) 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.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
@@ -353,6 +352,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> =>
new Promise<Opt<DocumentView>>(res => {
if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false);
+ if (doc === this.Document) return res(this.DocumentView?.());
const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
findDoc(dv => res(dv));
});
@@ -480,7 +480,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView?.())!);
const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 };
- const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
+ const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? dropActionType.embed : undefined);
de.moveDocument = this._props.moveDocument;
de.offset = this.screenToFreeformContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
DragManager.StartDocumentDrag(
@@ -582,7 +582,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
clusterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
if (doc && this.childDocList?.includes(doc))
- switch (property) {
+ switch (property.split(':')[0]) {
case StyleProp.BackgroundColor:
const cluster = NumCast(doc?.layout_cluster);
if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
@@ -607,7 +607,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
trySelectCluster = (addToSel: boolean) => {
- if (this._hitCluster !== -1) {
+ if (addToSel && this._hitCluster !== -1) {
!addToSel && SelectionManager.DeselectAll();
const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
this.selectDocuments(eles);
@@ -665,7 +665,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
x: B.x - inkWidth / 2,
y: B.y - inkWidth / 2,
_width: B.width + inkWidth,
- _height: B.height + inkWidth }, // prettier-ignore
+ _height: B.height + inkWidth,
+ stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore
inkWidth
);
if (Doc.ActiveTool === InkTool.Write) {
@@ -778,7 +779,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text));
};
- @action
onPointerMove = (e: PointerEvent) => {
if (this.tryDragCluster(e, this._hitCluster)) {
e.stopPropagation(); // we're moving a cluster, so stop propagation and return true to end panning and let the document drag take over
@@ -859,10 +859,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (excludeT < startSegmentT || excludeT > docCurveTVal) {
const localStartTVal = startSegmentT - Math.floor(i / 4);
t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t));
- segment.length && segments.push(segment);
+ if (segment.length && (Math.abs(segment[0].points[0].x - segment[0].points.lastElement().x) > 0.5 || Math.abs(segment[0].points[0].y - segment[0].points.lastElement().y) > 0.5)) segments.push(segment);
}
// start a new segment from the intersection t value
- segment = tVals.length - 1 === index ? [inkSegment.split(t).right] : [];
+ if (tVals.length - 1 === index) {
+ const split = inkSegment.split(t).right;
+ if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) segment = [split];
+ else segment = [];
+ } else segment = [];
startSegmentT = docCurveTVal;
});
} else {
@@ -1162,23 +1166,29 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
isContentActive = () => this._props.isContentActive();
- @undoBatch
+ /**
+ * Create a new text note of the same style as the one being typed into.
+ * If the text doc is be part of a larger templated doc, the new Doc will be a copy of the templated Doc
+ *
+ * @param fieldProps render props for the text doc being typed into
+ * @param below whether to place the new text Doc below or to the right of the one being typed into.
+ * @returns whether the new text doc was created and added successfully
+ */
+ createTextDocCopy = undoable((fieldProps: FieldViewProps, below: boolean) => {
+ const textDoc = DocCast(fieldProps.Document.rootDocument, fieldProps.Document);
+ const newDoc = Doc.MakeCopy(textDoc, true);
+ newDoc[DocData][Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
+ newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
+ newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
+ FormattedTextBox.SetSelectOnLoad(newDoc);
+ FormattedTextBox.DontSelectInitialText = true;
+ return this.addDocument?.(newDoc);
+ }, 'copied text note');
+
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
if ((e.metaKey || e.ctrlKey || e.altKey || fieldProps.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
e.stopPropagation?.();
- const below = !e.altKey && e.key !== 'Tab';
- const layout_fieldKey = StrCast(fieldProps.fieldKey);
- const newDoc = Doc.MakeCopy(fieldProps.Document, true);
- const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
- newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
- if (below) newDoc.y = NumCast(fieldProps.Document.y) + NumCast(fieldProps.Document._height) + 10;
- else newDoc.x = NumCast(fieldProps.Document.x) + NumCast(fieldProps.Document._width) + 10;
- if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
- newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
- }
- newDoc[DocData].text = undefined;
- FormattedTextBox.SetSelectOnLoad(newDoc);
- return this.addDocument?.(newDoc);
+ return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab');
}
};
@computed get childPointerEvents() {
@@ -1200,7 +1210,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const childLayout = entry.pair.layout;
const childData = entry.pair.data;
return (
- <CollectionFreeFormDocumentViewWrapper
+ <CollectionFreeFormDocumentView
{...OmitKeys(entry, ['replica', 'pair']).omit}
key={childLayout[Id] + (entry.replica || '')}
Document={childLayout}
@@ -1271,7 +1281,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._lightboxDoc = doc;
return true;
}
- if (this.childDocList?.includes(doc)) {
+ if (doc === this.Document || this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc)) {
if (doc.hidden) doc.hidden = false;
return true;
}
@@ -1296,15 +1306,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const rotation = Cast(_rotation,'number',
!this.layoutDoc._rotation_jitter ? null
: NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
+ const childProps = { ...this._props, fieldKey: '', styleProvider: this.clusterStyleProvider };
return {
x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
z: Cast(z, 'number'),
autoDim,
rotation,
- color: Cast(color, 'string') ? StrCast(color) : this._props.styleProvider?.(childDoc, this._props, StyleProp.Color),
- backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this._props, StyleProp.BackgroundColor),
- opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._props.styleProvider?.(childDoc, this._props, StyleProp.Opacity),
+ color: Cast(color, 'string') ? StrCast(color) : this.clusterStyleProvider(childDoc, childProps, StyleProp.Color),
+ backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, childProps, StyleProp.BackgroundColor),
+ opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.clusterStyleProvider?.(childDoc, childProps, StyleProp.Opacity),
zIndex: Cast(zIndex, 'number'),
width: _width,
height: _height,
@@ -1363,7 +1374,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
renderCutoffProvider = computedFn(
function renderCutoffProvider(this: any, doc: Doc) {
- return !this._renderCutoffData.get(doc[Id] + '');
+ return this.Document.isTemplateDoc ? false : !this._renderCutoffData.get(doc[Id] + '');
}.bind(this)
);
@@ -1414,7 +1425,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
// create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
- const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.Document });
+ const anchor = Docs.Create.ConfigDocument({
+ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection),
+ layout_unrendered: true,
+ presentation_transition: 500,
+ annotationOn: this.Document,
+ });
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, this.Document);
if (addAsAnnotation) {
@@ -1427,8 +1443,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return anchor;
};
- @action closeInfo = () => (this.Document._hideInfo = true);
- infoUI = () => (this.Document._hideInfo || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
+ @action closeInfo = () => (Doc.IsInfoUIDisabled = true);
+ infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
componentDidMount() {
this._props.setContentViewBox?.(this);
@@ -1486,7 +1502,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (!code.includes('dashDiv')) {
const script = CompileScript(code, { params: { docView: 'any' }, typecheck: false, editable: true });
if (script.compiled) script.run({ this: this.DocumentView?.() });
- } else code && !first && eval(code);
+ } else code && !first && eval?.(code);
},
{ fireImmediately: true }
);
@@ -1768,7 +1784,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!this._props.isAnnotationOverlay &&
!Doc.noviceMode &&
optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
- this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' });
+ this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor)), icon: 'palette' });
this._props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' });
// Want to condense into a Smart Organize button
this._props.renderDepth && optionItems.push({ description: 'Style with AI', event: this.gptStyling, icon: 'paint-brush' });
@@ -1828,7 +1844,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line
vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]); // right line
});
- SnappingManager.addSnapLines(horizLines, vertLines);
+ this.layoutDoc._freeform_snapLines && SnappingManager.addSnapLines(horizLines, vertLines);
};
incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
@@ -1836,7 +1852,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
incrementalRender = action(() => {
if (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.())) {
const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
- const loadIncrement = 5;
+ const loadIncrement = this.Document.isTemplateDoc ? Number.MAX_VALUE : 5;
for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
this._renderCutoffData.set(layout_unrendered[i][Id] + '', true);
}
@@ -1978,7 +1994,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
width: `${100 / (this.nativeDimScaling || 1)}%`,
height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
}}>
- {this.paintFunc ? null : this._lightboxDoc ? (
+ {this.paintFunc ? (
+ <FormattedTextBox {...this.props} /> // need this so that any live dashfieldviews will update the underlying text that the code eval reads
+ ) : this._lightboxDoc ? (
<div style={{ padding: 15, width: '100%', height: '100%' }}>
<DocumentView
{...this._props}
@@ -2087,8 +2105,8 @@ ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
for (let i = 0; i < children.length; i++) {
let eachRow = [];
for (let j = 0; j < keys.length; j++) {
- var cell = children[i][keys[j]];
- if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+ var cell = children[i][keys[j]]?.toString();
+ if (cell) cell = cell.toString().replace(/\,/g, '');
eachRow.push(cell);
}
csvRows.push(eachRow);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index a417d777a..6eca91e9d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -101,13 +101,15 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
};
@action
- onKeyPress = (e: KeyboardEvent) => {
+ onKeyDown = (e: KeyboardEvent) => {
//make textbox and add it to this collection
// tslint:disable-next-line:prefer-const
const cm = ContextMenu.Instance;
const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
if (e.key === '?') {
- cm.setDefaultItem('?', (str: string) => this._props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
+ cm.setDefaultItem('?', (str: string) =>
+ this._props.addDocTab(Docs.Create.WebDocument(`https://wikipedia.org/wiki/${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: `wiki:${str}`, data_useCors: true }), OpenWhere.addRight)
+ );
cm.displayMenu(this._downX, this._downY, undefined, true);
e.stopPropagation();
} else if (e.key === 'u' && this._props.ungroup) {
@@ -178,7 +180,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
} else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views.length < 2) {
FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this._props.childLayoutString ? e.key : '';
FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
- this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this._props.xPadding === 0));
+ this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100));
e.stopPropagation();
}
};
@@ -309,7 +311,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
const effectiveAcl = GetEffectiveAcl(this._props.Document[DocData]);
if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
PreviewCursor.Instance.Doc = doc;
- PreviewCursor.Show(x, y, this.onKeyPress, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
+ PreviewCursor.Show(x, y, this.onKeyDown, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
}
this.clearSelection();
}
@@ -494,11 +496,10 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
followLinkToggle: true,
_width: 200,
_height: 200,
- _layout_fitContentsToBox: true,
_layout_showSidebar: true,
title: 'overview',
});
- const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
+ const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });
portal.hidden = true;
@@ -635,8 +636,13 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
}
MarqueeRef: HTMLDivElement | null = null;
+ /**
+ * This is called for every drag movement when a document is dragged over this collection.
+ * If the document is dragged within 25 pixels of the edge of the collection and paused, this will
+ * auto scroll the collection so that it can be dragged farther (unless auto panning has been disabled)
+ */
@action
- onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
+ onDragMovePause = (e: CustomEvent<React.DragEvent>) => {
if ((e as any).handlePan || this._props.isAnnotationOverlay) return;
(e as any).handlePan = true;
@@ -659,7 +665,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
<div
className="marqueeView"
ref={r => {
- r?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
+ r?.addEventListener('dashDragMovePause', this.onDragMovePause as any);
this.MarqueeRef = r;
}}
style={{
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index b181b59ce..125dd2781 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,10 +1,10 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { Button } from 'browndash-components';
-import { action, computed, makeObservable } from 'mobx';
+import { Button, IconButton } from 'browndash-components';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse } from '../../../../Utils';
+import { FaChevronRight } from 'react-icons/fa';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { DragManager, dropActionType } from '../../../util/DragManager';
@@ -37,6 +37,8 @@ const resizerWidth = 8;
@observer
export class CollectionMulticolumnView extends CollectionSubView() {
+ @observable _startIndex = 0;
+
constructor(props: any) {
super(props);
makeObservable(this);
@@ -48,7 +50,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
*/
@computed
private get ratioDefinedDocs() {
- return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, '*') === DimUnit.Ratio);
+ return this.childLayouts.filter(layout => StrCast(layout._dimUnit, '*') === DimUnit.Ratio);
}
@computed
@@ -57,6 +59,15 @@ export class CollectionMulticolumnView extends CollectionSubView() {
return ratioDocs.length ? Math.min(...ratioDocs.map(layout => NumCast(layout._dimMagnitude))) : 1;
}
+ @computed get maxShown() {
+ return NumCast(this.layoutDoc.layout_maxShown);
+ }
+
+ @computed
+ private get childLayouts() {
+ return (this.maxShown ? this.childLayoutPairs.slice(this._startIndex, this._startIndex + this.maxShown) : this.childLayoutPairs).map(pair => pair.layout);
+ }
+
/**
* This loops through all childLayoutPairs and extracts the values for _dimUnit
* and _dimMagnitude, ignoring any that are malformed. Additionally, it then
@@ -69,9 +80,9 @@ export class CollectionMulticolumnView extends CollectionSubView() {
private get resolvedLayoutInformation(): LayoutData {
let starSum = 0;
const widthSpecifiers: WidthSpecifier[] = [];
- this.childLayoutPairs.map(pair => {
- const unit = StrCast(pair.layout._dimUnit, '*');
- const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim);
+ this.childLayouts.map(layout => {
+ const unit = StrCast(layout._dimUnit, '*');
+ const magnitude = NumCast(layout._dimMagnitude, this.minimumDim);
if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
unit === DimUnit.Ratio && (starSum += magnitude);
widthSpecifiers.push({ magnitude, unit });
@@ -90,7 +101,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
*/
// setTimeout(() => {
// const { ratioDefinedDocs } = this;
- // if (this.childLayoutPairs.length) {
+ // if (this.childPairs.length) {
// const minimum = this.minimumDim;
// if (minimum !== 0) {
// ratioDefinedDocs.forEach(layout => layout._dimMagnitude = NumCast(layout._dimMagnitude, 1) / minimum, 1);
@@ -187,13 +198,14 @@ export class CollectionMulticolumnView extends CollectionSubView() {
return Transform.Identity(); // we're still waiting on promises to resolve
}
let offset = 0;
- for (const { layout: candidate } of this.childLayoutPairs) {
+ var xf = Transform.Identity();
+ this.childLayouts.map(candidate => {
if (candidate === layout) {
- return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
+ return (xf = this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0));
}
offset += this.lookupPixels(candidate) + resizerWidth;
- }
- return Transform.Identity(); // type coersion, this case should never be hit
+ });
+ return xf;
};
@undoBatch
@@ -297,15 +309,15 @@ export class CollectionMulticolumnView extends CollectionSubView() {
*/
@computed
private get contents(): JSX.Element[] | null {
- const { childLayoutPairs } = this;
const collector: JSX.Element[] = [];
- for (let i = 0; i < childLayoutPairs.length; i++) {
- const { layout } = childLayoutPairs[i];
+ this.childLayouts.forEach((layout, i) => {
collector.push(
<Tooltip title={'Tab: ' + StrCast(layout.title)} key={'wrapper' + i}>
<div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}>
{this.getDisplayDoc(layout)}
- <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+ {this.layoutDoc._chromeHidden ? null : (
+ <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+ )}
<WidthLabel layout={layout} collectionDoc={this.Document} />
</div>
</Tooltip>,
@@ -317,10 +329,10 @@ export class CollectionMulticolumnView extends CollectionSubView() {
select={this._props.select}
columnUnitLength={this.getColumnUnitLength}
toLeft={layout}
- toRight={childLayoutPairs[i + 1]?.layout}
+ toRight={this.childLayouts[i + 1]}
/>
);
- }
+ });
collector.pop(); // removes the final extraneous resize bar
return collector;
}
@@ -339,6 +351,20 @@ export class CollectionMulticolumnView extends CollectionSubView() {
marginBottom: NumCast(this.Document._yMargin),
}}>
{this.contents}
+ {!this._startIndex ? null : (
+ <Tooltip title="scroll back">
+ <div style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
+ <Button tooltip="Scroll back" icon={<FontAwesomeIcon icon="chevron-left" size="lg" />} onClick={action(e => (this._startIndex = Math.max(0, this._startIndex - this.maxShown)))} color={SettingsManager.userColor} />
+ </div>
+ </Tooltip>
+ )}
+ {this._startIndex > this.childLayoutPairs.length - 1 || !this.maxShown ? null : (
+ <Tooltip title="scroll forward">
+ <div style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
+ <IconButton icon={<FaChevronRight />} color={SettingsManager.userColor} />
+ </div>
+ </Tooltip>
+ )}
</div>
);
}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 659f7ccdc..17bf3e50c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -1,7 +1,6 @@
import { action, computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse } from '../../../../Utils';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { DragManager, dropActionType } from '../../../util/DragManager';
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index fed4e89cf..9768877ff 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -38,91 +38,97 @@
background: yellow;
}
}
+ }
- .schema-column-menu,
- .schema-filter-menu {
- background: $light-gray;
- position: absolute;
- min-width: 200px;
- max-width: 400px;
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- z-index: 1;
-
- .schema-key-search-input {
- width: calc(100% - 20px);
- margin: 10px;
- }
+ .schema-preview-divider {
+ height: 100%;
+ background: black;
+ cursor: ew-resize;
+ }
+}
- .schema-search-result {
- cursor: pointer;
- padding: 5px 10px;
- width: 100%;
+.schema-column-menu,
+.schema-filter-menu {
+ background: $light-gray;
+ position: relative;
+ min-width: 200px;
+ max-width: 400px;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ z-index: 1;
- &:hover {
- background-color: $medium-gray;
- }
+ .schema-key-search-input {
+ width: calc(100% - 20px);
+ margin: 10px;
+ }
- .schema-search-result-type,
- .schema-search-result-desc {
- color: $dark-gray;
- font-size: $body-text;
- }
- }
+ .schema-search-result {
+ cursor: pointer;
+ padding: 5px 10px;
+ width: 100%;
- .schema-key-search,
- .schema-new-key-options {
- width: 100%;
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- }
+ &:hover {
+ background-color: $medium-gray;
+ }
+ .schema-search-result-type {
+ font-family: 'Courier New', Courier, monospace;
+ }
- .schema-new-key-options {
- margin: 10px;
- .schema-key-warning {
- color: red;
- font-weight: normal;
- align-self: center;
- }
- }
+ .schema-search-result-type,
+ .schema-search-result-desc {
+ color: $dark-gray;
+ font-size: $body-text;
+ }
+ .schema-search-result-desc {
+ font-style: italic;
+ }
+ }
- .schema-key-list {
- width: 100%;
- max-height: 300px;
- overflow-y: auto;
- }
+ .schema-key-search,
+ .schema-new-key-options {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ }
- .schema-key-type-option {
- margin: 2px 0px;
+ .schema-new-key-options {
+ margin: 10px;
+ .schema-key-warning {
+ color: red;
+ font-weight: normal;
+ align-self: center;
+ }
+ }
- input {
- margin-right: 5px;
- }
- }
+ .schema-key-list {
+ width: 100%;
+ max-height: 300px;
+ overflow-y: auto;
+ }
- .schema-key-default-val {
- margin: 5px 0;
- }
+ .schema-key-type-option {
+ margin: 2px 0px;
- .schema-column-menu-button {
- cursor: pointer;
- padding: 2px 5px;
- background: $medium-blue;
- border-radius: 9999px;
- color: $white;
- width: fit-content;
- margin: 5px;
- align-self: center;
- }
+ input {
+ margin-right: 5px;
}
}
- .schema-preview-divider {
- height: 100%;
- background: black;
- cursor: ew-resize;
+ .schema-key-default-val {
+ margin: 5px 0;
+ }
+
+ .schema-column-menu-button {
+ cursor: pointer;
+ padding: 2px 5px;
+ background: $medium-blue;
+ border-radius: 9999px;
+ color: $white;
+ width: fit-content;
+ margin: 5px;
+ align-self: center;
}
}
@@ -133,6 +139,7 @@
.row-menu {
display: flex;
justify-content: center;
+ cursor: pointer;
}
}
@@ -219,6 +226,15 @@
align-items: center;
}
+.schemaRTFCell {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ min-width: 10px;
+ min-height: 10px;
+}
+
.schema-row {
cursor: grab;
justify-content: flex-end;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 31b4a2dd4..6a956f2ac 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,31 +1,34 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, makeObservable, observable, ObservableMap, observe } from 'mobx';
+import { Popup, PopupTrigger, Type } from 'browndash-components';
+import { ObservableMap, action, computed, makeObservable, observable, observe } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
+import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
-import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Documents';
+import { DocUtils, Docs, DocumentOptions, FInfo } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from '../../../util/DragManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
import { SelectionManager } from '../../../util/SelectionManager';
-import { undoable, undoBatch } from '../../../util/UndoManager';
+import { SettingsManager } from '../../../util/SettingsManager';
+import { undoBatch, undoable } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { EditableView } from '../../EditableView';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../../nodes/DocumentView';
-import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView';
+import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
import { KeyValueBox } from '../../nodes/KeyValueBox';
-import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionSchemaView.scss';
import { SchemaColumnHeader } from './SchemaColumnHeader';
import { SchemaRowBox } from './SchemaRowBox';
-const { default: { SCHEMA_NEW_NODE_HEIGHT } } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
+const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
export enum ColumnType {
Number,
@@ -161,7 +164,7 @@ export class CollectionSchemaView extends CollectionSubView() {
(change as any).added.forEach((doc: Doc) => // for each document added
Doc.GetAllPrototypes(doc.value as Doc).forEach(proto => // for all of its prototypes (and itself)
Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them
- !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo(key, key === 'author'))))));
+ !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo("-no description-", key === 'author'))))));
break;
case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
}
@@ -235,7 +238,7 @@ export class CollectionSchemaView extends CollectionSubView() {
}
break;
case 'Backspace': {
- this.removeDocument(this._selectedDocs);
+ undoable(() => this.removeDocument(this._selectedDocs), 'delete schema row');
break;
}
case 'Escape': {
@@ -282,7 +285,7 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal));
+ addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[DocData][key] = defaultVal));
@undoBatch
removeColumn = (index: number) => {
@@ -681,6 +684,7 @@ export class CollectionSchemaView extends CollectionSubView() {
} else {
this.setKey(this._menuValue, this._newFieldDefault);
}
+ this._columnMenuIndex = undefined;
})}>
done
</div>
@@ -688,12 +692,12 @@ export class CollectionSchemaView extends CollectionSubView() {
);
}
- onPassiveWheel = (e: WheelEvent) => {
+ onKeysPassiveWheel = (e: WheelEvent) => {
// 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)
- if (!this._oldWheel.scrollTop && e.deltaY <= 0) e.preventDefault();
+ if (!this._oldKeysWheel.scrollTop && e.deltaY <= 0) e.preventDefault();
e.stopPropagation();
};
- _oldWheel: any;
+ _oldKeysWheel: any;
@computed get keysDropdown() {
return (
<div className="schema-key-search">
@@ -708,9 +712,9 @@ export class CollectionSchemaView extends CollectionSubView() {
<div
className="schema-key-list"
ref={r => {
- this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
- this._oldWheel = r;
- r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ this._oldKeysWheel?.removeEventListener('wheel', this.onKeysPassiveWheel);
+ this._oldKeysWheel = r;
+ r?.addEventListener('wheel', this.onKeysPassiveWheel, { passive: false });
}}>
{this._menuKeys.map(key => (
<div
@@ -721,14 +725,14 @@ export class CollectionSchemaView extends CollectionSubView() {
}}>
<p>
<span className="schema-search-result-key">
- {key}
- {this.fieldInfos.get(key)!.fieldType ? ', ' : ''}
+ <b>{key}</b>
+ {this.fieldInfos.get(key)!.fieldType ? ':' : ''}
</span>
<span className="schema-search-result-type" style={{ color: this.fieldInfos.get(key)!.readOnly ? 'red' : 'inherit' }}>
{this.fieldInfos.get(key)!.fieldType}
</span>
+ <span className="schema-search-result-desc">&nbsp;&nbsp;{this.fieldInfos.get(key)!.description}</span>
</p>
- <p className="schema-search-result-desc">{this.fieldInfos.get(key)!.description}</p>
</div>
))}
</div>
@@ -740,16 +744,17 @@ export class CollectionSchemaView extends CollectionSubView() {
const x = this._columnMenuIndex! == -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
return (
<div className="schema-column-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}>
- <input className="schema-key-search-input" type="text" value={this._menuValue} onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
+ <input className="schema-key-search-input" type="text" onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
+ {this._makeNewField ? this.newFieldMenu : this.keysDropdown}
+ </div>
+ );
+ }
+ get renderKeysMenu() {
+ console.log('RNDERMENUT:' + this._columnMenuIndex);
+ return (
+ <div className="schema-column-menu" style={{ left: 0, minWidth: CollectionSchemaView._minColWidth }}>
+ <input className="schema-key-search-input" type="text" onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
{this._makeNewField ? this.newFieldMenu : this.keysDropdown}
- <div
- className="schema-column-menu-button"
- onPointerDown={action(e => {
- e.stopPropagation();
- this.closeColumnMenu();
- })}>
- cancel
- </div>
</div>
);
}
@@ -833,6 +838,8 @@ export class CollectionSchemaView extends CollectionSubView() {
isContentActive = () => this._props.isSelected() || this._props.isContentActive();
screenToLocal = () => this.ScreenToLocalBoxXf().translate(-this.tableWidth, 0);
previewWidthFunc = () => this.previewWidth;
+ onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
+ _oldWheel: any;
render() {
return (
<div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}>
@@ -841,15 +848,23 @@ export class CollectionSchemaView extends CollectionSubView() {
className="schema-table"
style={{ width: `calc(100% - ${this.previewWidth}px)` }}
onWheel={e => this._props.isContentActive() && e.stopPropagation()}
- ref={r => {
- // prevent wheel events from passively propagating up through containers
- r?.addEventListener('wheel', (e: WheelEvent) => {}, { passive: false });
+ ref={ele => {
+ // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
+ this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+ (this._oldWheel = ele)?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
}}>
<div className="schema-header-row" style={{ height: this.rowHeightFunc() }}>
<div className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth }}>
- <div className="schema-header-button" onPointerDown={e => (this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true))}>
- <FontAwesomeIcon icon="plus" />
- </div>
+ <Popup
+ placement="right"
+ background={SettingsManager.userBackgroundColor}
+ color={SettingsManager.userColor}
+ toggle={<FontAwesomeIcon onPointerDown={e => this.openColumnMenu(-1, true)} icon="plus" />}
+ trigger={PopupTrigger.CLICK}
+ type={Type.TERT}
+ isOpen={this._columnMenuIndex !== -1 ? false : undefined}
+ popup={this.renderKeysMenu}
+ />
</div>
{this.columnKeys.map((key, index) => (
<SchemaColumnHeader
@@ -870,7 +885,7 @@ export class CollectionSchemaView extends CollectionSubView() {
/>
))}
</div>
- {this._columnMenuIndex !== undefined && this.renderColumnMenu}
+ {this._columnMenuIndex !== undefined && this._columnMenuIndex !== -1 && this.renderColumnMenu}
{this._filterColumnIndex !== undefined && this.renderFilterMenu}
<CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} rowHeight={this.rowHeightFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} />
{this.layoutDoc.chromeHidden ? null : (
@@ -981,7 +996,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
styleProvider={this.noOpacityStyleProvider}
waitForDoubleClickToClick={returnNever}
defaultDoubleClick={returnIgnore}
- dragAction="move"
+ dragAction={dropActionType.move}
onClickScriptDisable="always"
focus={this._props.schema.focusDocument}
childFilters={this._props.schema.childDocFilters}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 001ad5ab6..bf36b2668 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,8 +1,11 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Popup, Size, Type } from 'browndash-components';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
import * as React from 'react';
import DatePicker from 'react-datepicker';
+import 'react-datepicker/dist/react-datepicker.css';
import Select from 'react-select';
import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
import { DateField } from '../../../../fields/DateField';
@@ -10,8 +13,11 @@ import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { RichTextField } from '../../../../fields/RichTextField';
import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
-import { FInfo } from '../../../documents/Documents';
+import { FInfo, FInfoFieldType } from '../../../documents/Documents';
import { DocFocusOrOpen } from '../../../util/DocumentManager';
+import { dropActionType } from '../../../util/DragManager';
+import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, undoable } from '../../../util/UndoManager';
import { EditableView } from '../../EditableView';
@@ -24,11 +30,6 @@ import { KeyValueBox } from '../../nodes/KeyValueBox';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { ColumnType, FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
-import 'react-datepicker/dist/react-datepicker.css';
-import { Popup, Size, Type } from 'browndash-components';
-import { IconLookup, faCaretDown } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { SettingsManager } from '../../../util/SettingsManager';
export interface SchemaTableCellProps {
Document: Doc;
@@ -50,6 +51,8 @@ export interface SchemaTableCellProps {
options?: string[];
menuTarget: HTMLDivElement | null;
transform: () => Transform;
+ autoFocus?: boolean; // whether to set focus on creation, othwerise wait for a click
+ rootSelected?: () => boolean;
}
@observer
@@ -86,18 +89,20 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
isSelected: returnFalse,
setHeight: returnFalse,
select: emptyFunction,
- dragAction: 'move',
+ dragAction: dropActionType.move,
renderDepth: 1,
+ noSidebar: true,
isContentActive: returnFalse,
whenChildContentsActiveChanged: emptyFunction,
ScreenToLocalTransform: Transform.Identity,
focus: emptyFunction,
addDocTab: SchemaTableCell.addFieldDoc,
pinToPres: returnZero,
- Document,
+ Document: DocCast(Document.rootDocument, Document),
fieldKey: fieldKey,
PanelWidth: columnWidth,
PanelHeight: props.rowHeight,
+ rootSelected: props.rootSelected,
};
const readOnly = getFinfo(fieldKey)?.readOnly ?? false;
const cursor = !readOnly ? 'text' : 'default';
@@ -123,17 +128,20 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
pointerEvents,
}}>
<EditableView
+ ref={r => this.selected && this._props.autoFocus && r?.setIsFocused(true)}
oneLine={this._props.oneLine}
allowCRs={this._props.allowCRs}
contents={undefined}
fieldContents={fieldProps}
editing={this.selected ? undefined : false}
- GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
+ GetValue={() => Field.toKeyValueString(fieldProps.Document, this._props.fieldKey, SnappingManager.MetaKey)}
SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => {
if (shiftDown && enterKey) {
this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
+ this._props.finishEdit?.();
+ return true;
}
- const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value);
+ const ret = KeyValueBox.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(fieldProps.Document) ? true : undefined);
this._props.finishEdit?.();
return ret;
}, 'edit schema cell')}
@@ -149,7 +157,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
if (cellValue instanceof DateField) return ColumnType.Date;
if (cellValue instanceof RichTextField) return ColumnType.RTF;
if (typeof cellValue === 'number') return ColumnType.Any;
- if (typeof cellValue === 'string' && columnTypeStr !== 'enumeration') return ColumnType.Any;
+ if (typeof cellValue === 'string' && columnTypeStr !== FInfoFieldType.enumeration) return ColumnType.Any;
if (typeof cellValue === 'boolean') return ColumnType.Boolean;
if (columnTypeStr && columnTypeStr in FInfotoColType) {
@@ -205,7 +213,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
get url() {
const field = Cast(this._props.Document[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
- const alts = DocListCast(this._props.Document[this._props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
+ const alts = DocListCast(this._props.Document[this._props.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
const altpaths = alts
.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
.filter(url => url)
@@ -311,13 +319,15 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps
const selected: [Doc, number] | undefined = this._props.selectedCell();
return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
}
+
+ // if the text box blurs and none of its contents are focused(), then the edit finishes
selectedFunc = () => this.selected;
render() {
const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
fieldProps.isContentActive = this.selectedFunc;
return (
- <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}>
- {this.selected ? <FormattedTextBox {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+ <div className="schemaRTFCell" style={{ fontStyle: this.selected ? undefined : 'italic', color, textDecoration, cursor, pointerEvents }}>
+ {this.selected ? <FormattedTextBox {...fieldProps} autoFocus={true} onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
</div>
);
}
@@ -344,8 +354,7 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp
onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => {
if ((value?.nativeEvent as any).shiftKey) {
this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
- }
- KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+ } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
})}
/>
<EditableView
@@ -356,8 +365,10 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp
SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => {
if (shiftDown && enterKey) {
this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
+ this._props.finishEdit?.();
+ return true;
}
- const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value);
+ const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined);
this._props.finishEdit?.();
return set;
})}