aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx154
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx9
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx42
-rw-r--r--src/client/views/collections/CollectionMenu.tsx26
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.scss20
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx71
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx67
-rw-r--r--src/client/views/collections/CollectionPileView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss2
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx122
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx68
-rw-r--r--src/client/views/collections/CollectionSubView.tsx22
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx39
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx44
-rw-r--r--src/client/views/collections/CollectionView.tsx112
-rw-r--r--src/client/views/collections/TabDocView.tsx63
-rw-r--r--src/client/views/collections/TreeView.tsx179
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx9
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx629
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx12
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx9
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.scss10
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx34
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx8
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx6
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx683
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx513
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx138
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx152
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss707
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx1241
-rw-r--r--src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx62
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx136
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTable.tsx694
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx67
36 files changed, 1963 insertions, 4191 deletions
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 01f41869e..a266c9207 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -5,11 +5,11 @@ import * as React from 'react';
import { Doc } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { OmitKeys, returnFalse, Utils } from '../../../Utils';
+import { returnFalse, returnZero, Utils } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { DocumentView } from '../nodes/DocumentView';
import { StyleProp } from '../StyleProvider';
-import "./CollectionCarousel3DView.scss";
+import './CollectionCarousel3DView.scss';
import { CollectionSubView } from './CollectionSubView';
@observer
@@ -20,134 +20,146 @@ export class CollectionCarousel3DView extends CollectionSubView() {
private _dropDisposer?: DragManager.DragDropDisposer;
- componentWillUnmount() { this._dropDisposer?.(); }
+ componentWillUnmount() {
+ this._dropDisposer?.();
+ }
- protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view
+ protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+ //used for stacking and masonry view
this._dropDisposer?.();
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
}
- }
+ };
panelWidth = () => this.props.PanelWidth() / 3;
panelHeight = () => this.props.PanelHeight() * 0.6;
onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
+ isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
+ isChildContentActive = () => (this.isContentActive() ? true : false);
+
@computed get content() {
const currentIndex = NumCast(this.layoutDoc._itemIndex);
- const displayDoc = (childPair: { layout: Doc, data: Doc }) => {
- return <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "childLayoutTemplate", "childLayoutString"]).omit}
- onDoubleClick={this.onChildDoubleClick}
- renderDepth={this.props.renderDepth + 1}
- LayoutTemplate={this.props.childLayoutTemplate}
- LayoutTemplateString={this.props.childLayoutString}
- Document={childPair.layout}
- DataDoc={childPair.data}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
- bringToFront={returnFalse}
- />;
+ const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
+ return (
+ <DocumentView
+ {...this.props}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ suppressSetHeight={true}
+ onDoubleClick={this.onChildDoubleClick}
+ renderDepth={this.props.renderDepth + 1}
+ LayoutTemplate={this.props.childLayoutTemplate}
+ LayoutTemplateString={this.props.childLayoutString}
+ Document={childPair.layout}
+ DataDoc={childPair.data}
+ isContentActive={this.isChildContentActive}
+ isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ bringToFront={returnFalse}
+ />
+ );
};
- return (this.childLayoutPairs.map((childPair, index) => {
+ return this.childLayoutPairs.map((childPair, index) => {
return (
- <div key={childPair.layout[Id]}
- className={`collectionCarousel3DView-item${index === currentIndex ? "-active" : ""} ${index}`}
- style={index === currentIndex ?
- { opacity: '1', transform: 'scale(1.3)', width: this.panelWidth() } :
- { opacity: '0.5', transform: 'scale(0.6)', userSelect: 'none', width: this.panelWidth() }}>
+ <div
+ key={childPair.layout[Id]}
+ className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`}
+ style={index === currentIndex ? { opacity: '1', transform: 'scale(1.3)', width: this.panelWidth() } : { opacity: '0.5', transform: 'scale(0.6)', userSelect: 'none', width: this.panelWidth() }}>
{displayDoc(childPair)}
- </div>);
- }));
+ </div>
+ );
+ });
}
changeSlide = (direction: number) => {
this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length;
- }
+ };
onArrowClick = (e: React.MouseEvent, direction: number) => {
e.stopPropagation();
this.changeSlide(direction);
- !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = (direction === 1) ? "fwd" : "back"); // while autoscroll is on, keep the other autoscroll button hidden
+ !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = direction === 1 ? 'fwd' : 'back'); // while autoscroll is on, keep the other autoscroll button hidden
!this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on
- }
+ };
interval?: number;
startAutoScroll = (direction: number) => {
this.interval = window.setInterval(() => {
this.changeSlide(direction);
}, this.scrollSpeed);
- }
+ };
stopAutoScroll = () => {
window.clearInterval(this.interval);
this.interval = undefined;
this.fadeScrollButton();
- }
+ };
toggleAutoScroll = (direction: number) => {
this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true;
this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll();
- }
+ };
fadeScrollButton = () => {
window.setTimeout(() => {
- !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = "none"); //fade away after 1.5s if it's not clicked.
+ !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); //fade away after 1.5s if it's not clicked.
}, 1500);
- }
+ };
@computed get buttons() {
if (!this.props.isContentActive()) return null;
- return <div className="arrow-buttons" >
- <div key="back" className="carousel3DView-back" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
- onClick={(e) => this.onArrowClick(e, -1)}
- >
- <FontAwesomeIcon icon={"angle-left"} size={"2x"} />
- </div>
- <div key="fwd" className="carousel3DView-fwd" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
- onClick={(e) => this.onArrowClick(e, 1)}
- >
- <FontAwesomeIcon icon={"angle-right"} size={"2x"} />
+ return (
+ <div className="arrow-buttons">
+ <div key="back" className="carousel3DView-back" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={e => this.onArrowClick(e, -1)}>
+ <FontAwesomeIcon icon={'angle-left'} size={'2x'} />
+ </div>
+ <div key="fwd" className="carousel3DView-fwd" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={e => this.onArrowClick(e, 1)}>
+ <FontAwesomeIcon icon={'angle-right'} size={'2x'} />
+ </div>
+ {this.autoScrollButton}
</div>
- {this.autoScrollButton}
- </div>;
+ );
}
@computed get autoScrollButton() {
const whichButton = this.layoutDoc.showScrollButton;
- return <>
- <div className={`carousel3DView-back-scroll${whichButton === "back" ? "" : "-hidden"}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
- onClick={() => this.toggleAutoScroll(-1)}>
- {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={"pause"} size={"1x"} /> : <FontAwesomeIcon icon={"angle-double-left"} size={"1x"} />}
- </div>
- <div className={`carousel3DView-fwd-scroll${whichButton === "fwd" ? "" : "-hidden"}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
- onClick={() => this.toggleAutoScroll(1)}>
- {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={"pause"} size={"1x"} /> : <FontAwesomeIcon icon={"angle-double-right"} size={"1x"} />}
- </div>
- </>;
+ return (
+ <>
+ <div className={`carousel3DView-back-scroll${whichButton === 'back' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(-1)}>
+ {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-left'} size={'1x'} />}
+ </div>
+ <div className={`carousel3DView-fwd-scroll${whichButton === 'fwd' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(1)}>
+ {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-right'} size={'1x'} />}
+ </div>
+ </>
+ );
}
@computed get dots() {
- return (this.childLayoutPairs.map((_child, index) =>
- <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._itemIndex) ? "-active" : ""}`}
- onClick={() => this.layoutDoc._itemIndex = index} />));
+ return this.childLayoutPairs.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._itemIndex) ? '-active' : ''}`} onClick={() => (this.layoutDoc._itemIndex = index)} />);
}
render() {
const index = NumCast(this.layoutDoc._itemIndex);
const translateX = this.panelWidth() * (1 - index);
- return <div className="collectionCarousel3DView-outer" ref={this.createDashEventsTarget}
- style={{
- background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
- color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
- }} >
- <div className="carousel-wrapper" style={{ transform: `translateX(${translateX}px)` }}>
- {this.content}
- </div>
- {this.props.Document._chromeHidden ? (null) : this.buttons}
- <div className="dot-bar">
- {this.dots}
+ return (
+ <div
+ className="collectionCarousel3DView-outer"
+ ref={this.createDashEventsTarget}
+ style={{
+ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
+ color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
+ }}>
+ <div className="carousel-wrapper" style={{ transform: `translateX(${translateX}px)` }}>
+ {this.content}
+ </div>
+ {this.props.Document._chromeHidden ? null : this.buttons}
+ <div className="dot-bar">{this.dots}</div>
</div>
- </div>;
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 32f6207ed..0e4556eb4 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Opt } from '../../../fields/Doc';
import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { OmitKeys, returnFalse } from '../../../Utils';
+import { emptyFunction, returnFalse, returnZero } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -47,7 +47,7 @@ export class CollectionCarouselView extends CollectionSubView() {
@computed get content() {
const index = NumCast(this.layoutDoc._itemIndex);
const curDoc = this.childLayoutPairs?.[index];
- const captionProps = { ...OmitKeys(this.props, ['setHeight']).omit, fieldKey: 'caption' };
+ const captionProps = { ...this.props, fieldKey: 'caption', setHeight: undefined };
const marginX = NumCast(this.layoutDoc['caption-xMargin']);
const marginY = NumCast(this.layoutDoc['caption-yMargin']);
const showCaptions = StrCast(this.layoutDoc._showCaption);
@@ -55,12 +55,13 @@ export class CollectionCarouselView extends CollectionSubView() {
<>
<div className="collectionCarouselView-image" key="image">
<DocumentView
- {...OmitKeys(this.props, ['setHeight', 'NativeWidth', 'NativeHeight', 'childLayoutTemplate', 'childLayoutString']).omit}
+ {...this.props}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
onDoubleClick={this.onContentDoubleClick}
onClick={this.onContentClick}
hideCaptions={showCaptions ? true : false}
renderDepth={this.props.renderDepth + 1}
- ContainingCollectionView={this.props.CollectionView}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
Document={curDoc.layout}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1ead80bd0..4d000542c 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -26,12 +26,14 @@ import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
import { TabDocView } from './TabDocView';
import React = require('react');
import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { OverlayView } from '../OverlayView';
+import { ScriptingRepl } from '../ScriptingRepl';
const _global = (window /* browser */ || global) /* node */ as any;
@observer
export class CollectionDockingView extends CollectionSubView() {
@observable public static Instance: CollectionDockingView | undefined;
- public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) {
+ public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) {
return {
type: 'react-component',
component: 'DocumentFrameRenderer',
@@ -39,6 +41,7 @@ export class CollectionDockingView extends CollectionSubView() {
width: width,
props: {
documentId: document[Id],
+ keyValue,
panelName, // name of tab that can be used to close or replace its contents
},
};
@@ -143,10 +146,10 @@ export class CollectionDockingView extends CollectionSubView() {
@undoBatch
@action
- public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean): boolean {
+ public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean, keyValue?: boolean): boolean {
const instance = CollectionDockingView.Instance;
if (!instance) return false;
- const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
+ const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue);
if (!panelName && stack) {
const activeContentItemIndex = stack.contentItems.findIndex((item: any) => item.config === stack._activeContentItem.config);
const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout);
@@ -168,10 +171,10 @@ export class CollectionDockingView extends CollectionSubView() {
}
@undoBatch
- public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string) {
+ public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1
? CollectionDockingView.CloseSplit(doc)
- : CollectionDockingView.AddSplit(doc, location, stack, panelName);
+ : CollectionDockingView.AddSplit(doc, location, stack, panelName, keyValue);
}
//
@@ -179,10 +182,10 @@ export class CollectionDockingView extends CollectionSubView() {
//
@undoBatch
@action
- public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string) {
+ public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
if (document?._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document);
if (!CollectionDockingView.Instance) return false;
- const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document);
+ const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document && !keyValue);
if (tab) {
tab.header.parent.setActiveContentItem(tab.contentItem);
return true;
@@ -190,7 +193,7 @@ export class CollectionDockingView extends CollectionSubView() {
const instance = CollectionDockingView.Instance;
const glayRoot = instance._goldenLayout.root;
if (!instance) return false;
- const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
+ const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue);
if (!pullSide && stack) {
stack.addChild(docContentConfig, undefined);
@@ -407,11 +410,10 @@ export class CollectionDockingView extends CollectionSubView() {
const _width = Number(getComputedStyle(content).width.replace('px', ''));
const _height = Number(getComputedStyle(content).height.replace('px', ''));
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 img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: this.rootDoc.title + '-icon', _width, _height, _nativeWidth, _nativeHeight });
const proto = this.dataDoc; // Cast(img.proto, Doc, null)!;
proto['thumb-nativeWidth'] = _width;
proto['thumb-nativeHeight'] = _height;
- this.dataDoc.thumb = new ImageField(iconFile);
+ proto.thumb = new ImageField(iconFile);
});
}
}
@@ -580,19 +582,25 @@ ScriptingGlobals.add(
'(doc: any)'
);
ScriptingGlobals.add(
- function openOnRight(doc: any) {
- return CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
+ function openDoc(doc: any, where: OpenWhere) {
+ switch (where) {
+ case OpenWhere.addRight:
+ return CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
+ case OpenWhere.overlay:
+ if (doc === 'repl') OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
+ else Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+ }
},
- 'opens up document in tab on right side of the screen',
+ 'opens up document in location specified',
'(doc: any)'
);
ScriptingGlobals.add(
- function openInOverlay(doc: any) {
- return Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+ function openRepl() {
+ return 'openRepl';
},
'opens up document in screen overlay layer',
'(doc: any)'
);
-ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) {
- CollectionDockingView.ReplaceTab(doc, OpenWhereMod.right, undefined, shiftKey);
+ScriptingGlobals.add(function useRightSplit(doc: any, addToRightSplit?: boolean) {
+ CollectionDockingView.ReplaceTab(doc, OpenWhereMod.right, undefined, addToRightSplit);
});
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index c83f4e689..5b631676e 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -120,7 +120,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
isSelected={returnFalse}
docViewPath={returnEmptyDoclist}
moveDocument={returnFalse}
- CollectionView={undefined}
addDocument={returnFalse}
addDocTab={returnFalse}
pinToPres={emptyFunction}
@@ -134,8 +133,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
/>
</div>
);
@@ -246,23 +243,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
immediate: undoBatch((source: Doc[]) => {}),
initialize: emptyFunction,
};
- _openLinkInCommand = {
- params: ['target', 'container'],
- title: 'link follow target',
- script: `{ if (self.container?.length) {
- getProto(self.target).linkContainer = self.container[0];
- getProto(self.target).isLinkButton = true;
- getProto(self.target).onClick = makeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])");
- }}`,
- immediate: undoBatch((container: Doc[]) => {
- if (container.length) {
- Doc.GetProto(this.target).linkContainer = container[0];
- Doc.GetProto(this.target).isLinkButton = true;
- Doc.GetProto(this.target).onClick = ScriptField.MakeScript('getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])');
- }
- }),
- initialize: emptyFunction,
- };
_viewCommand = {
params: ['target'],
title: 'bookmark view',
@@ -328,7 +308,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand];
}
@computed get _doc_commands() {
- return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand];
+ return Doc.noviceMode ? undefined : [this._onClickCommand];
}
@computed get _tree_commands() {
return undefined;
@@ -659,8 +639,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
key="float"
style={{
backgroundColor: this.props.docView.layoutDoc.z ? '121212' : undefined,
- pointerEvents: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? 'none' : undefined,
- color: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? 'dimgrey' : undefined,
+ pointerEvents: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._viewType !== CollectionViewType.Freeform ? 'none' : undefined,
+ color: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._viewType !== CollectionViewType.Freeform ? 'dimgrey' : undefined,
}}
onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}>
<FontAwesomeIcon icon={['fab', 'buffer']} size={'lg'} />
diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss
index 4d1f18e54..be1800d81 100644
--- a/src/client/views/collections/CollectionNoteTakingView.scss
+++ b/src/client/views/collections/CollectionNoteTakingView.scss
@@ -51,10 +51,6 @@
display: flex;
}
-.collectionNoteTakingViewFieldColumn {
- display: flex;
- overflow: auto;
-}
.collectionNoteTakingViewFieldColumn:hover {
.collectionNoteTakingView-DocumentButtons {
display: flex;
@@ -98,6 +94,11 @@
position: relative;
display: block;
}
+ .collectionNoteTakingViewFieldColumn {
+ overflow: auto;
+ display: flex;
+ flex-direction: column;
+ }
.collectionSchemaView-previewDoc {
height: 100%;
@@ -111,14 +112,19 @@
height: auto;
}
+ .collectionNoteTakingView-columnStackContainer {
+ height: 100%;
+ overflow: auto;
+ width: 100%;
+ display: flex;
+ }
.collectionNoteTakingView-columnStack {
height: 100%;
width: 100%;
- display: inline-block;
+ display: table-cell;
}
.collectionNoteTakingView-Nodes {
width: 100%;
- height: 100%;
position: absolute;
display: flex;
flex-direction: column;
@@ -128,7 +134,6 @@
width: 100%;
position: absolute;
margin: auto;
- width: max-content;
height: max-content;
position: relative;
grid-auto-rows: 0px;
@@ -165,6 +170,7 @@
// Documents in NoteTaking view
.collectionNoteTakingView-columnDoc {
display: flex;
+ width: 100%;
// margin: auto; // Removed auto so that it is no longer center aligned - this could be something we change
position: relative;
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 26c73438c..99d4d0bee 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -3,7 +3,7 @@ import { CursorProperty } from 'csstype';
import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { DataSym, Doc, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
-import { Copy, Id } from '../../../fields/FieldSymbols';
+import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
@@ -11,7 +11,6 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Ty
import { TraceMobx } from '../../../fields/util';
import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
@@ -19,7 +18,7 @@ import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { LightboxView } from '../LightboxView';
-import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { StyleProp } from '../StyleProvider';
@@ -55,11 +54,11 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset'));
if (needsUnsetCategory || columnHeaders === undefined || columnHeaders.length === 0) {
setTimeout(() => {
- const columnHeaders = Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null);
+ const columnHeaders = Array.from(Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null) ?? []);
const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset'));
- if (needsUnsetCategory || columnHeaders === undefined || columnHeaders.length === 0) {
- if (columnHeaders) columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1));
- else this.dataDoc.columnHeaders = new List<SchemaHeaderField>();
+ if (needsUnsetCategory || columnHeaders.length === 0) {
+ columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1));
+ this.resizeColumns(columnHeaders);
}
});
}
@@ -163,7 +162,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
}
@action
- moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
};
@@ -187,20 +186,16 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// let's dive in and get the actual document we want to drag/move around
focusDocument = (doc: Doc, options: DocFocusOptions) => {
Doc.BrushDoc(doc);
- let focusSpeed = 0;
const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
const top = found.getBoundingClientRect().top;
const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
if (Math.floor(localTop[1]) !== 0) {
- smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
+ let focusSpeed = options.zoomTime ?? 500;
+ smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
+ return focusSpeed;
}
}
- const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing);
- this.props.focus(this.rootDoc, {
- ...options,
- afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)),
- });
};
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
@@ -217,9 +212,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ blockPointerEventsWhenDragging = () => (this.docsDraggedRowCol.length ? 'none' : undefined);
// getDisplayDoc returns the rules for displaying a document in this view (ie. DocumentView)
getDisplayDoc(doc: Doc, width: () => number) {
- const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
+ const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc;
const height = () => this.getDocHeight(doc);
let dref: Opt<DocumentView>;
const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
@@ -227,6 +223,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
<DocumentView
ref={r => (dref = r || undefined)}
Document={doc}
+ pointerEvents={this.blockPointerEventsWhenDragging}
DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
renderDepth={this.props.renderDepth + 1}
PanelWidth={width}
@@ -258,8 +255,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
hideTitle={this.props.childHideTitle?.()}
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
removeDocument={this.props.removeDocument}
@@ -276,7 +271,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// getDocTransform is used to get the coordinates of a document when we go from a view like freeform to columns
getDocTransform(doc: Doc, dref?: DocumentView) {
const y = this._scroll; // required for document decorations to update when the text box container is scrolled
- const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined);
+ const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined);
// the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off
return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale);
}
@@ -296,7 +291,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
getDocHeight(d?: Doc) {
if (!d || d.hidden) return 0;
const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
- const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc;
+ const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0);
const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0);
@@ -319,18 +314,16 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// Removing example: column widths are [0.5, 0.30, 0.20] --> user deletes the final column --> column widths are [0.625, 0.375].
// Adding example: column widths are [0.6, 0.4] --> user adds column at end --> column widths are [0.4, 0.267, 0.33]
@action
- resizeColumns = (isAdd: boolean, colWidth: number, colIndex: number) => {
- const n = this.columnHeaders.length;
- if (n == 1) {
- this.columnHeaders[0].setWidth(1);
- return true;
- }
- const scaleFactor = isAdd ? 1 - colWidth : 1 / (1 - colWidth);
- this.columnHeaders.forEach((h, i) => {
- if (!(isAdd && i == colIndex)) {
- h.width < 0 ? h.setWidth(1 / n) : h.setWidth(h.width * scaleFactor);
- }
- });
+ resizeColumns = (headers: SchemaHeaderField[]) => {
+ const n = headers.length;
+ const curWidths = headers.reduce((sum, hdr) => sum + Math.abs(hdr.width), 0);
+ const scaleFactor = 1 / curWidths;
+ this.dataDoc.columnHeaders = new List<SchemaHeaderField>(
+ headers.map(h => {
+ h.setWidth(Math.abs(h.width) * scaleFactor);
+ return h;
+ })
+ );
return true;
};
@@ -446,7 +439,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
} else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' });
this.props.addDocument?.(source);
- de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed
+ de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { linkRelationship: 'doc annotation' }); // TODODO this is where in text links get passed
e.stopPropagation();
} else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
return false;
@@ -520,6 +513,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
this.observer.observe(ref);
}
}}
+ select={this.props.select}
addDocument={this.addDocument}
chromeHidden={this.chromeHidden}
columnHeaders={this.columnHeaders}
@@ -533,7 +527,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
dividerWidth={this.DividerWidth}
maxColWidth={this.maxColWidth}
availableWidth={this.availableWidth}
- PanelWidth={this.PanelWidth}
key={heading?.heading ?? 'unset'}
headings={this.headings}
heading={heading?.heading ?? 'unset'}
@@ -555,16 +548,16 @@ export class CollectionNoteTakingView extends CollectionSubView() {
addGroup = (value: string) => {
if (this.columnHeaders) {
for (const header of this.columnHeaders) {
- if (header.heading == value) {
+ if (header.heading === value) {
alert('You cannot use an existing column name. Please try a new column name');
return value;
}
}
}
- const columnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null);
+ const columnHeaders = Array.from(Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null));
const newColWidth = 1 / (this.numGroupColumns + 1);
- value && columnHeaders?.push(new SchemaHeaderField(value, undefined, undefined, newColWidth)) && this.resizeColumns(true, newColWidth, this.columnHeaders.length - 1);
- this.dataDoc.columnHeaders = new List<SchemaHeaderField>(columnHeaders.map(header => header[Copy]()));
+ columnHeaders.push(new SchemaHeaderField(value, undefined, undefined, newColWidth));
+ value && this.resizeColumns(columnHeaders);
return true;
};
@@ -637,8 +630,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
docFilters={this.props.docFilters}
docRangeFilters={this.props.docRangeFilters}
searchFilterDocs={this.props.searchFilterDocs}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
/>
</div>
);
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 84d1c0205..28bdd0cb9 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -37,6 +37,7 @@ interface CSVFieldColumnProps {
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;
@@ -44,8 +45,7 @@ interface CSVFieldColumnProps {
observeHeight: (myref: any) => void;
unobserveHeight: (myref: any) => void;
editableViewProps: () => any;
- resizeColumns: (isAdd: boolean, colWidth: number, colIndex: number) => boolean;
- PanelWidth: number;
+ resizeColumns: (headers: SchemaHeaderField[]) => boolean;
maxColWidth: number;
dividerWidth: number;
availableWidth: number;
@@ -141,18 +141,11 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
@undoBatch
@action
deleteColumn = () => {
- const acolumnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null);
- if (acolumnHeaders && this.props.headingObject) {
- const index = acolumnHeaders.indexOf(this.props.headingObject);
- const columnHeaders = acolumnHeaders; // new List<SchemaHeaderField>(acolumnHeaders.map(header => header[Copy]())); // needed for undo to work properly. otherwise we end up changing field values in the undo stack since they are shared by reference
- const newColIndex = index > 0 ? index - 1 : 1;
- const newColHeader = this.props.columnHeaders ? this.props.columnHeaders[newColIndex] : undefined;
- const newHeading = newColHeader ? newColHeader.heading : 'unset';
- this.props.docList.forEach(d => (d[this.props.pivotField] = newHeading));
- const colWidth = this.props.columnHeaders ? this.props.columnHeaders[index].width : 0;
- columnHeaders.splice(index, 1);
- //Doc.GetProto(this.props.Document).columnHeaders = columnHeaders;
- this.props.resizeColumns(false, colWidth, index);
+ const columnHeaders = Array.from(Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null));
+ if (this.props.headingObject) {
+ this.props.docList.forEach(d => (d[this.props.pivotField] = undefined));
+ columnHeaders.splice(columnHeaders.indexOf(this.props.headingObject), 1);
+ this.props.resizeColumns(columnHeaders);
}
};
@@ -248,7 +241,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
className="collectionNoteTakingView-sectionHeader-subCont"
title={evContents === `No Value` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
style={{ background: evContents !== `No Value` ? this._color : 'inherit' }}>
- <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
+ <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this.props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
</div>
{(this.props.columnHeaders?.length ?? 0) > 1 && (
<button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
@@ -262,28 +255,30 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
return (
<>
{headingView}
- <div className="collectionNoteTakingView-columnStack">
- <div
- key={`${heading}-stack`}
- className={`collectionNoteTakingView-Nodes`}
- style={{
- padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
- gridGap: this.props.gridGap,
- gridTemplateColumns: templatecols,
- }}>
- {this.props.renderChildren(this.props.docList)}
- </div>
+ <div className="collectionNoteTakingView-columnStackContainer">
+ <div className="collectionNoteTakingView-columnStack">
+ <div
+ key={`${heading}-stack`}
+ className={`collectionNoteTakingView-Nodes`}
+ style={{
+ padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
+ gridGap: this.props.gridGap,
+ gridTemplateColumns: templatecols,
+ }}>
+ {this.props.renderChildren(this.props.docList)}
+ </div>
- {!this.props.chromeHidden && type !== DocumentType.PRES ? (
- <div className="collectionNoteTakingView-DocumentButtons" style={{ marginBottom: 10 }}>
- <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
- <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
- </div>
- <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton">
- <EditableView {...this.props.editableViewProps()} />
+ {!this.props.chromeHidden ? (
+ <div className="collectionNoteTakingView-DocumentButtons" style={{ marginBottom: 10 }}>
+ <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
+ <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
+ </div>
+ <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton">
+ <EditableView {...this.props.editableViewProps()} />
+ </div>
</div>
- </div>
- ) : null}
+ ) : null}
+ </div>
</div>
</>
);
@@ -294,7 +289,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
const heading = this._heading;
return (
<div
- className={'collectionNoteTakingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')}
+ className="collectionNoteTakingViewFieldColumn"
key={heading}
style={{
width: this.columnWidth,
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index ba90ed8cd..fd9b0c0ce 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -30,7 +30,7 @@ export class CollectionPileView extends CollectionSubView() {
// pileups are designed to go away when they are empty.
this._disposers.selected = reaction(
() => this.childDocs.length,
- num => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document)
+ num => !num && this.props.CollectionFreeFormDocumentView?.().props.removeDocument?.(this.props.Document)
);
}
componentWillUnmount() {
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index 5a107d2ca..a55b70e22 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -97,7 +97,7 @@
top: 2.5%;
height: 95%;
border-radius: 4px;
- background: $light-gray;
+ //background: $light-gray;
&:hover {
opacity: 1;
}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 28f08b6ce..22a575989 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -2,19 +2,20 @@ import React = require('react');
import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
-import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
+import { Doc, Opt, StrListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { Cast, NumCast } from '../../../fields/Types';
+import { Cast, NumCast, ScriptCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
+import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
-import { LinkFollower } from '../../util/LinkFollower';
+import { FollowLinkScript, IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower';
+import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -22,7 +23,6 @@ import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { AudioWaveform } from '../AudioWaveform';
import { CollectionSubView } from '../collections/CollectionSubView';
-import { Colors } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
import { LabelBox } from '../nodes/LabelBox';
@@ -54,7 +54,7 @@ export enum TrimScope {
@observer
export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
@observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
- @observable public static CurrentlyPlaying: Doc[];
+ @observable public static CurrentlyPlaying: DocumentView[];
static RangeScript: ScriptField;
static LabelScript: ScriptField;
@@ -179,18 +179,21 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
if (
// need to include range inputs because after dragging video time slider it becomes target element
!(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
- this.props.isSelected(true)
+ this.props.isContentActive()
) {
// if shift pressed scrub 1 second otherwise 1/10th
const jump = e.shiftKey ? 1 : 0.1;
switch (e.key) {
case ' ':
+ this.props.playing() ? this.props.Pause() : this.props.Play();
+ break;
+ case '^':
if (!CollectionStackedTimeline.SelectingRegion) {
this._markerStart = this._markerEnd = this.currentTime;
CollectionStackedTimeline.SelectingRegion = this;
} else {
this._markerEnd = this.currentTime;
- CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd, undefined, true);
+ CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
this._markerEnd = undefined;
CollectionStackedTimeline.SelectingRegion = undefined;
}
@@ -257,7 +260,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._markerEnd = tmp;
}
if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) {
- const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd, undefined, true);
+ const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false));
}
(!isClick || !wasSelecting) && (this._markerEnd = undefined);
@@ -273,7 +276,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
undefined,
() => {
if (shiftKey) {
- CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime, undefined, undefined, true);
+ CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.currentTime, undefined, undefined, true);
} else {
!wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
@@ -388,19 +391,22 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
// creates marker on timeline
@undoBatch
@action
- static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) {
+ static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) {
if (anchorStartTime === undefined) return rootDoc;
+ const startTag = '_timecodeToShow';
+ const endTag = '_timecodeToHide';
const anchor =
docAnchor ??
Docs.Create.LabelDocument({
title: ComputedField.MakeFunction(`self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])`) as any,
_minFontSize: 12,
_maxFontSize: 24,
- _singleLine: false,
+ _singleLine: true,
_stayInCollection: true,
+ backgroundColor: 'rgba(128, 128, 128, 0.5)',
useLinkSmallAnchor: true,
hideLinkButton: true,
- _isLinkButton: true,
+ onClick: FollowLinkScript(),
annotationOn: rootDoc,
_timelineLabel: true,
borderRounding: anchorEndTime === undefined ? '100%' : undefined,
@@ -419,7 +425,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@action
playOnClick = (anchorDoc: Doc, clientX: number) => {
- const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25;
+ const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
const endTime = this.anchorEnd(anchorDoc);
if (this.layoutDoc.autoPlayAnchors) {
if (this.props.playing()) this.props.Pause();
@@ -444,10 +450,10 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@action
clickAnchor = (anchorDoc: Doc, clientX: number) => {
- if (anchorDoc.isLinkButton) {
- LinkFollower.FollowLink(undefined, anchorDoc, this.props, false);
+ if (IsFollowLinkScript(anchorDoc.onClick)) {
+ LinkFollower.FollowLink(undefined, anchorDoc, false);
}
- const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25;
+ const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
const endTime = this.anchorEnd(anchorDoc);
if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) {
if (this.props.playing()) this.props.Pause();
@@ -505,37 +511,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
currentTimecode = () => this.currentTime;
- @computed get renderDictation() {
- const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null);
- return !dictation ? null : (
- <div
- style={{
- position: 'absolute',
- height: '100%',
- top: this.timelineContentHeight,
- background: Colors.LIGHT_BLUE,
- }}>
- <DocumentView
- {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
- Document={dictation}
- PanelHeight={this.dictationHeight}
- isAnnotationOverlay={true}
- isDocumentActive={returnFalse}
- select={emptyFunction}
- NativeDimScaling={returnOne}
- xMargin={25}
- yMargin={10}
- ScreenToLocalTransform={this.dictationScreenToLocalTransform}
- whenChildContentsActiveChanged={emptyFunction}
- removeDocument={returnFalse}
- moveDocument={returnFalse}
- addDocument={returnFalse}
- CollectionView={undefined}
- renderDepth={this.props.renderDepth + 1}></DocumentView>
- </div>
- );
- }
-
// renders selection region on timeline
@computed get selectionContainer() {
const markerEnd = CollectionStackedTimeline.SelectingRegion === this ? this.currentTime : this._markerEnd;
@@ -561,7 +536,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
anchor,
}));
const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
- return (
+ return this.clipDuration === 0 ? null : (
<div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : undefined }}>
<div
className="collectionStackedTimeline-timelineContainer"
@@ -594,10 +569,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
top,
width: `${width}px`,
height: `${height}px`,
- }}
- onClick={e => {
- this.props.playFrom(start, this.anchorEnd(d.anchor));
- e.stopPropagation();
+ pointerEvents: 'none',
}}>
<StackedTimelineAnchor
{...this.props}
@@ -634,7 +606,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
PanelWidth={this.timelineContentWidth}
/>
)}
- {/* {this.renderDictation} */}
<div
className="collectionStackedTimeline-hover"
@@ -738,19 +709,19 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
() => this.props.currentTimecode(),
time => {
const dictationDoc = Cast(this.props.layoutDoc['data-dictation'], Doc, null);
- const isDictation = dictationDoc && DocListCast(this.props.mark.links).some(link => Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc);
+ const isDictation = dictationDoc && LinkManager.Links(this.props.mark).some(link => Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc);
if (
!LightboxView.LightboxDoc &&
// bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront.
// for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
/*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/
!this.props.layoutDoc.dontAutoFollowLinks &&
- DocListCast(this.props.mark.links).length &&
+ LinkManager.Links(this.props.mark).length &&
time > NumCast(this.props.mark[this.props.startTag]) &&
time < NumCast(this.props.mark[this.props.endTag]) &&
this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5
) {
- LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false);
+ LinkFollower.FollowLink(undefined, this.props.mark, false);
}
this._lastTimecode = time;
}
@@ -761,9 +732,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
this._disposer?.();
}
+ @observable noEvents = false;
// starting the drag event for anchor resizing
+ @action
onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
- this.props._timeline?.setPointerCapture(e.pointerId);
+ //this.props._timeline?.setPointerCapture(e.pointerId);
const newTime = (e: PointerEvent) => {
const rect = (e.target as any).getBoundingClientRect();
return this.props.toTimeline(e.clientX - rect.x, rect.width);
@@ -779,8 +752,8 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
}
return false;
};
+ this.noEvents = true;
var undo: UndoManager.Batch | undefined;
-
setupMoveUpEvents(
this,
e,
@@ -789,11 +762,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
this.props.setTime(newTime(e));
return changeAnchor(anchor, left, newTime(e));
},
- e => {
+ action(e => {
this.props.setTime(newTime(e));
- this.props._timeline?.releasePointerCapture(e.pointerId);
undo?.end();
- },
+ this.noEvents = false;
+ }),
emptyFunction
);
};
@@ -811,19 +784,23 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
// renders anchor LabelBox
renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
const anchor = observable({ view: undefined as any });
- const focusFunc = (doc: Doc, options: DocFocusOptions) => {
+ const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => {
this.props.playLink(mark);
- this.props.focus(doc, options);
+ return undefined;
};
return {
anchor,
view: (
<DocumentView
key="view"
- {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+ {...this.props}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
ref={action((r: DocumentView | null) => (anchor.view = r))}
Document={mark}
DataDoc={undefined}
+ docViewPath={returnEmptyDoclist}
+ pointerEvents={this.noEvents ? returnNone : undefined}
styleProvider={this.props.styleProvider}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={undefined}
@@ -833,7 +810,14 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
PanelHeight={height}
fitWidth={returnTrue}
ScreenToLocalTransform={screenXf}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ whenChildContentsActiveChanged={emptyFunction}
focus={focusFunc}
+ isContentActive={returnFalse}
+ searchFilterDocs={returnEmptyDoclist}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
rootSelected={returnFalse}
onClick={script}
onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
@@ -854,15 +838,15 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
render() {
const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height);
return (
- <>
+ <div style={{ pointerEvents: this.noEvents ? 'none' : undefined }}>
{inner.view}
{!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? null : (
<>
- <div key="left" className="collectionStackedTimeline-left-resizer" onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} />
- <div key="right" className="collectionStackedTimeline-resizer" onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} />
+ <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} />
+ <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} />
</>
)}
- </>
+ </div>
);
}
}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6314b4529..bdad325d5 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -3,14 +3,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { CursorProperty } from 'csstype';
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
import { DragManager, dropActionType } from '../../util/DragManager';
@@ -22,7 +22,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from '../EditableView';
import { LightboxView } from '../LightboxView';
import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { StyleProp } from '../StyleProvider';
@@ -201,6 +201,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
componentDidMount() {
super.componentDidMount?.();
+ this.props.setContentView?.(this);
// reset section headers when a new filter is inputted
this._pivotFieldDisposer = reaction(
@@ -226,8 +227,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this._autoHeightDisposer?.();
}
+ isAnyChildContentActive = () => this.props.isAnyChildContentActive();
+
@action
- moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
+ 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) => {
@@ -250,20 +253,17 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
focusDocument = (doc: Doc, options: DocFocusOptions) => {
Doc.BrushDoc(doc);
- let focusSpeed = 0;
const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
const top = found.getBoundingClientRect().top;
const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
if (Math.floor(localTop[1]) !== 0) {
- smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
+ let focusSpeed = options.zoomTime ?? 500;
+ smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
+ return focusSpeed;
}
}
- const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing;
- this.props.focus(this.rootDoc, {
- ...options,
- afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)),
- });
+ return undefined;
};
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
@@ -305,7 +305,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// this is what renders the document that you see on the screen
// called in Children: this actually adds a document to our children list
getDisplayDoc(doc: Doc, width: () => number, count: number) {
- const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
+ const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc;
const height = () => this.getDocHeight(doc);
let dref: Opt<DocumentView>;
@@ -323,8 +323,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
styleProvider={this.styleProvider}
docViewPath={this.props.docViewPath}
fitWidth={this.props.childFitWidth}
- isContentActive={doc.isLinkButton ? this.isChildButtonContentActive : this.isChildContentActive}
+ isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
onKey={this.onKeyDown}
+ onBrowseClick={this.props.onBrowseClick}
isDocumentActive={this.isContentActive}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
@@ -345,8 +346,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
hideTitle={this.props.childHideTitle?.()}
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
xPadding={NumCast(this.layoutDoc._childXPadding, this.props.childXPadding)}
yPadding={NumCast(this.layoutDoc._childYPadding, this.props.childYPadding)}
addDocument={this.props.addDocument}
@@ -380,7 +379,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
getDocHeight(d?: Doc) {
if (!d || d.hidden) return 0;
const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
- const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc;
+ const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0);
const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0);
@@ -459,7 +458,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
} else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' });
this.props.addDocument?.(source);
- de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed
+ de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { linkRelationship: 'doc annotation' }); // TODODO this is where in text links get passed
e.stopPropagation();
} else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
return false;
@@ -622,11 +621,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
if (!e.isPropagationStopped()) {
- const subItems: ContextMenuProps[] = [];
- subItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
- subItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
- subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' });
- ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' });
+ const cm = ContextMenu.Instance;
+ const options = cm.findByDescription('Options...');
+ const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
+ optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
+ optionItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
+ optionItems.push({ description: 'Clear All', event: () => (this.dataDoc[this.fieldKey ?? 'data'] = new List([])), icon: 'times' });
+ !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
}
};
@@ -654,11 +655,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
<DocumentView
Document={menuDoc}
DataDoc={menuDoc}
- isContentActive={this.props.isContentActive}
- isDocumentActive={returnTrue}
+ isContentActive={this.isContentActive}
+ isDocumentActive={this.isContentActive}
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
addDocTab={this.props.addDocTab}
+ onBrowseClick={this.props.onBrowseClick}
pinToPres={emptyFunction}
rootSelected={this.props.isSelected}
removeDocument={this.props.removeDocument}
@@ -674,8 +676,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
docFilters={this.props.docFilters}
docRangeFilters={this.props.docRangeFilters}
searchFilterDocs={this.props.searchFilterDocs}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
/>
</div>
);
@@ -719,28 +719,20 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
className={this.isStackingView ? 'collectionStackingView' : 'collectionMasonryView'}
ref={this.createRef}
style={{
- overflowY: this.props.isContentActive() ? 'auto' : 'hidden',
+ overflowY: this.isContentActive() ? 'auto' : 'hidden',
background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
- pointerEvents: this.backgroundEvents ? 'all' : undefined,
+ pointerEvents: (this.props.pointerEvents?.() as any) ?? (this.backgroundEvents ? 'all' : undefined),
}}
onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
onDrop={this.onExternalDrop.bind(this)}
onContextMenu={this.onContextMenu}
- onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}>
+ onWheel={e => this.isContentActive() && e.stopPropagation()}>
{this.renderedSections}
{!this.showAddAGroup ? null : (
<div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
<EditableView {...editableViewProps} />
</div>
)}
- {/* {this.chromeHidden || !this.props.isSelected() ? (null) :
- <Switch
- onChange={this.onToggle}
- onClick={this.onToggle}
- defaultChecked={true}
- checkedChildren="edit"
- unCheckedChildren="view"
- />} */}
</div>
</div>
</>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 27ae3041f..5581ac8fe 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,5 +1,4 @@
import { action, computed, observable } from 'mobx';
-import ReactLoading from 'react-loading';
import * as rp from 'request-promise';
import CursorField from '../../../fields/CursorField';
import { AclPrivate, Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
@@ -20,7 +19,6 @@ import { DocComponent } from '../DocComponent';
import React = require('react');
export interface SubCollectionViewProps extends CollectionViewProps {
- CollectionView: Opt<CollectionView>;
isAnyChildContentActive: () => boolean;
}
@@ -67,6 +65,7 @@ export function CollectionSubView<X>(moreProps?: X) {
// to its children which may be templates.
// If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
@computed get dataField() {
+ if (this.layoutDoc[this.props.fieldKey]) return this.layoutDoc[this.props.fieldKey];
// sets the dataDoc's data field to an empty list if the data field is undefined - prevents issues with addonly
// setTimeout changes it outside of the @computed section
!this.dataDoc[this.props.fieldKey] && setTimeout(() => !this.dataDoc[this.props.fieldKey] && (this.dataDoc[this.props.fieldKey] = new List<Doc>()));
@@ -79,7 +78,7 @@ export function CollectionSubView<X>(moreProps?: X) {
.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : undefined, doc))
.filter(pair => {
// filter out any documents that have a proto that we don't have permissions to
- return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));
+ return !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));
});
return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
@@ -201,8 +200,9 @@ export function CollectionSubView<X>(moreProps?: X) {
}
}
- addDocument = (doc: Doc | Doc[]) => this.props.addDocument?.(doc) || false;
-
+ addDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.addDocument?.(doc, annotationKey) || false;
+ removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.removeDocument?.(doc, annotationKey) || false;
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this.props.moveDocument?.(doc, targetCollection, addDocument);
@action
protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
const docDragData = de.complete.docDragData;
@@ -269,10 +269,10 @@ export function CollectionSubView<X>(moreProps?: X) {
if (FormattedTextBox.IsFragment(html)) {
const href = FormattedTextBox.GetHref(html);
if (href) {
- const docid = FormattedTextBox.GetDocFromUrl(href);
- if (docid) {
+ const docId = FormattedTextBox.GetDocFromUrl(href);
+ if (docId) {
// prosemirror text containing link to dash document
- DocServer.GetRefField(docid).then(f => {
+ DocServer.GetRefField(docId).then(f => {
if (f instanceof Doc) {
if (options.x || options.y) {
f.x = options.x as number;
@@ -311,8 +311,8 @@ export function CollectionSubView<X>(moreProps?: X) {
} else {
const path = window.location.origin + '/doc/';
if (text.startsWith(path)) {
- const docid = text.replace(Doc.globalServerPath(), '').split('?')[0];
- DocServer.GetRefField(docid).then(f => {
+ const docId = text.replace(Doc.globalServerPath(), '').split('?')[0];
+ DocServer.GetRefField(docId).then(f => {
if (f instanceof Doc) {
if (options.x || options.y) {
f.x = options.x as number;
@@ -335,7 +335,7 @@ export function CollectionSubView<X>(moreProps?: X) {
const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any;
if (focusNode) {
const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
- anchor && DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor });
+ anchor && DocUtils.MakeLink(htmlDoc, anchor, {});
}
}
}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index e6f29ec37..4d5978548 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -21,7 +21,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
import { CollectionSubView } from './CollectionSubView';
import './CollectionTimeView.scss';
import React = require('react');
-import { DocFocusOptions } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
import { PresBox } from '../nodes/trails';
@observer
@@ -52,8 +52,7 @@ export class CollectionTimeView extends CollectionSubView() {
title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
annotationOn: this.rootDoc,
});
- anchor.presPinPivotField = this.pivotField; // should be captured in pinDocView below
- PresBox.pinDocView(anchor, { pinData: { viewType: true, filters: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinData: { viewType: true, pivot: true, filters: true } }, this.rootDoc);
if (addAsAnnotation) {
// when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
@@ -67,32 +66,12 @@ export class CollectionTimeView extends CollectionSubView() {
};
@action
- scrollFocus = (anchor: Doc, options: DocFocusOptions) => {
- if (options.preview) {
- // if in preview, then override document's fields with view spec
- this._focusFilters = StrListCast(anchor.presPinDocFilters);
- this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters);
- this._focusPivotField = StrCast(anchor.presPinPivotField);
- return undefined;
- }
- const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
-
- // should be part of restoreTargetDocView
- this.layoutDoc._prevFilterIndex = 1;
- this.layoutDoc._pivotField = anchor.presPinPivotField;
-
- return PresBox.restoreTargetDocView(
- this.props.DocumentView?.(), //
- { pinDocLayout: BoolCast(anchor.presPinLayout) },
- anchor,
- focusSpeed,
- {
- viewType: anchor.presPinViewType ? true : false,
- filters: anchor.presPinDocFilters || anchor.presPinDocRangeFilters ? true : false,
- }
- )
- ? focusSpeed
- : undefined;
+ scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => {
+ // if in preview, then override document's fields with view spec
+ this._focusFilters = StrListCast(anchor.presDocFilters);
+ this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters);
+ this._focusPivotField = StrCast(anchor.presPivotField);
+ return undefined;
};
layoutEngine = () => this._layoutEngine;
@@ -171,7 +150,7 @@ export class CollectionTimeView extends CollectionSubView() {
engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }}
fitContentsToBox={returnTrue}
childClickScript={this._childClickedScript}
- viewDefDivClick={this._viewDefDivClick}
+ viewDefDivClick={this.layoutEngine() === computeTimelineLayout.name ? undefined : this._viewDefDivClick}
//dontScaleFilter={this.dontScaleFilter}
layoutEngine={this.layoutEngine}
/>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 330e116d1..f81c17a7b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -7,7 +7,7 @@ import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../Utils';
+import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
@@ -38,8 +38,8 @@ export type collectionTreeViewProps = {
onCheckedClick?: () => ScriptField;
onChildClick?: () => ScriptField;
// TODO: [AL] add these fields
- AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
- RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
hierarchyIndex?: number[];
};
@@ -164,7 +164,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
this.props.removeDocument?.(doc);
if (ind > 0) {
FormattedTextBox.SelectOnLoad = prev[Id];
- DocumentManager.Instance.getDocumentView(prev, this.props.CollectionView)?.select(false);
+ DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false);
}
return true;
}
@@ -242,8 +242,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={this.doc}
- ContainingCollectionView={this.props.CollectionView}
addDocument={returnFalse}
moveDocument={returnFalse}
removeDocument={returnFalse}
@@ -272,7 +270,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
this,
this.doc,
this.props.DataDoc,
- this.props.ContainingCollectionDoc,
+ undefined,
undefined,
addDoc,
this.remove,
@@ -331,10 +329,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
isDocumentActive={returnTrue}
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
+ removeDocument={this.props.removeDocument}
addDocTab={this.props.addDocTab}
pinToPres={emptyFunction}
rootSelected={this.props.isSelected}
- removeDocument={this.props.removeDocument}
ScreenToLocalTransform={Transform.Identity}
PanelWidth={this.return35}
PanelHeight={this.return35}
@@ -347,8 +345,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
docFilters={this.props.docFilters}
docRangeFilters={this.props.docRangeFilters}
searchFilterDocs={this.props.searchFilterDocs}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
/>
</div>
);
@@ -378,18 +374,18 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1));
- addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false;
- remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false;
+ addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this.props.fieldKey}-annotations`) || false;
+ remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false;
moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) =>
- this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false;
+ this.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false;
@observable _headerHeight = 0;
- contentFunc = () => {
+ @computed get content() {
const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined);
const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? null : this.titleBar;
- return [
- <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
+ 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/, ''))))}>
{this.buttonMenu}
@@ -428,9 +424,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
</div>
</div>
</div>
- </div>,
- ];
- };
+ </div>
+ );
+ }
render() {
TraceMobx();
@@ -439,7 +435,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
<div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
{!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? (
<CollectionFreeFormView
- {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ {...this.props}
+ setContentView={emptyFunction}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone}
isAnnotationOverlay={true}
isAnnotationOverlayScrollable={true}
childDocumentsActive={this.props.isDocumentActive}
@@ -451,10 +451,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
moveDocument={this.moveAnnotationDocument}
bringToFront={emptyFunction}
renderDepth={this.props.renderDepth + 1}>
- {this.contentFunc}
+ {this.content}
</CollectionFreeFormView>
) : (
- this.contentFunc()
+ this.content
)}
</div>
);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 90406ed3c..cfb9310b6 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -2,7 +2,6 @@ import { computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
-import { Id } from '../../../fields/FieldSymbols';
import { ObjectField } from '../../../fields/ObjectField';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -10,13 +9,12 @@ import { TraceMobx } from '../../../fields/util';
import { returnEmptyString } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
-import { BranchCreate, BranchTask } from '../../documents/Gitlike';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { InteractionUtils } from '../../util/InteractionUtils';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
-import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { OpenWhere } from '../nodes/DocumentView';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import { CollectionCarousel3DView } from './CollectionCarousel3DView';
import { CollectionCarouselView } from './CollectionCarouselView';
@@ -47,7 +45,7 @@ interface CollectionViewProps_ extends FieldViewProps {
// property overrides for child documents
childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
- childDocumentsActive?: () => boolean; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
+ childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
childFitWidth?: (child: Doc) => boolean;
childShowTitle?: () => string;
childOpacity?: () => number;
@@ -64,8 +62,8 @@ interface CollectionViewProps_ extends FieldViewProps {
childClickScript?: ScriptField;
childDoubleClickScript?: ScriptField;
//TODO: [AL] add these fields
- AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
- RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
}
export interface CollectionViewProps extends React.PropsWithChildren<CollectionViewProps_> {}
@@ -101,18 +99,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
return viewField as any as CollectionViewType;
}
- showIsTagged = () => {
- return null;
- // this section would display an icon in the bototm right of a collection to indicate that all
- // photos had been processed through Google's content analysis API and Google's tags had been
- // assigned to the documents googlePhotosTags field.
- // const children = DocListCast(this.rootDoc[this.props.fieldKey]);
- // const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto);
- // const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags);
- // return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />;
- //this.isContentActive();
- };
-
screenToLocalTransform = () => (this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth()));
// prettier-ignore
private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => {
@@ -140,26 +126,25 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
}
};
- setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) {
- const subItems: ContextMenuProps[] = [];
- subItems.push({ description: 'Freeform', event: () => func(CollectionViewType.Freeform), icon: 'signature' });
- if (addExtras && CollectionView._safeMode) {
- ContextMenu.Instance.addItem({ description: 'Test Freeform', event: () => func(CollectionViewType.Invalid), icon: 'project-diagram' });
- }
- subItems.push({ description: 'Schema', event: () => func(CollectionViewType.Schema), icon: 'th-list' });
- subItems.push({ description: 'Tree', event: () => func(CollectionViewType.Tree), icon: 'tree' });
- subItems.push({ description: 'Stacking', event: () => (func(CollectionViewType.Stacking)._autoHeight = true), icon: 'ellipsis-v' });
- subItems.push({ description: 'Notetaking', event: () => (func(CollectionViewType.NoteTaking)._autoHeight = true), icon: 'ellipsis-v' });
- subItems.push({ description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' });
- subItems.push({ description: 'Multirow', event: () => func(CollectionViewType.Multirow), icon: 'columns' });
- subItems.push({ description: 'Masonry', event: () => func(CollectionViewType.Masonry), icon: 'columns' });
- subItems.push({ description: 'Carousel', event: () => func(CollectionViewType.Carousel), icon: 'columns' });
- subItems.push({ description: '3D Carousel', event: () => func(CollectionViewType.Carousel3D), icon: 'columns' });
- !Doc.noviceMode && subItems.push({ description: 'Pivot/Time', event: () => func(CollectionViewType.Time), icon: 'columns' });
- !Doc.noviceMode && subItems.push({ description: 'Map', event: () => func(CollectionViewType.Map), icon: 'globe-americas' });
- subItems.push({ description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' });
-
+ setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc) {
if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) {
+ // prettier-ignore
+ const subItems: ContextMenuProps[] = [
+ { description: 'Freeform', event: () => func(CollectionViewType.Freeform), icon: 'signature' },
+ { description: 'Schema', event: () => func(CollectionViewType.Schema), icon: 'th-list' },
+ { description: 'Tree', event: () => func(CollectionViewType.Tree), icon: 'tree' },
+ { description: 'Stacking', event: () => (func(CollectionViewType.Stacking)._autoHeight = true), icon: 'ellipsis-v' },
+ { description: 'Notetaking', event: () => (func(CollectionViewType.NoteTaking)._autoHeight = true), icon: 'ellipsis-v' },
+ { description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' },
+ { description: 'Multirow', event: () => func(CollectionViewType.Multirow), icon: 'columns' },
+ { description: 'Masonry', event: () => func(CollectionViewType.Masonry), icon: 'columns' },
+ { description: 'Carousel', event: () => func(CollectionViewType.Carousel), icon: 'columns' },
+ { description: '3D Carousel', event: () => func(CollectionViewType.Carousel3D), icon: 'columns' },
+ { description: 'Pivot/Time', event: () => func(CollectionViewType.Time), icon: 'columns' },
+ { description: 'Map', event: () => func(CollectionViewType.Map), icon: 'globe-americas' },
+ { description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' },
+ ];
+
const existingVm = ContextMenu.Instance.findByDescription(category);
const catItems = existingVm && 'subitems' in existingVm ? existingVm.subitems : [];
catItems.push({ description: 'Add a Perspective...', addDivider: true, noexpand: true, subitems: subItems, icon: 'eye' });
@@ -169,18 +154,14 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
onContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
- if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== Doc.MainDocId) {
+ if (cm && !e.isPropagationStopped()) {
// need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- this.setupViewTypes(
- 'UI Controls...',
- vtype => {
- const newRendition = Doc.MakeAlias(this.rootDoc);
- newRendition._viewType = vtype;
- this.props.addDocTab(newRendition, OpenWhere.addRight);
- return newRendition;
- },
- false
- );
+ this.setupViewTypes('UI Controls...', vtype => {
+ const newRendition = Doc.MakeAlias(this.rootDoc);
+ newRendition._viewType = vtype;
+ this.props.addDocTab(newRendition, OpenWhere.addRight);
+ return newRendition;
+ });
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
@@ -191,25 +172,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) {
optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' });
}
- !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' });
-
- if (!Doc.noviceMode && false) {
- optionItems.push({
- description: 'Create Branch',
- event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), OpenWhere.addRight),
- icon: 'project-diagram',
- });
- optionItems.push({
- description: 'Pull Master',
- event: () => BranchTask(this.rootDoc, 'pull'),
- icon: 'project-diagram',
- });
- optionItems.push({
- description: 'Merge Branches',
- event: () => BranchTask(this.rootDoc, 'merge'),
- icon: 'project-diagram',
- });
- }
+ !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.rootDoc._isLightbox = !this.rootDoc._isLightbox), icon: 'project-diagram' });
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' });
@@ -255,11 +218,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles);
childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle);
childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null);
- @computed get childLayoutString() {
- return StrCast(this.rootDoc.childLayoutString, this.props.childLayoutString);
- }
-
- isContentActive = (outsideReaction?: boolean) => this.props.isContentActive();
+ isContentActive = (outsideReaction?: boolean) => this.props.isContentActive() || this.isAnyChildContentActive();
render() {
TraceMobx();
@@ -275,14 +234,15 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
PanelHeight: this.props.PanelHeight,
ScreenToLocalTransform: this.screenToLocalTransform,
childLayoutTemplate: this.childLayoutTemplate,
- childLayoutString: this.childLayoutString,
+ childLayoutString: StrCast(this.rootDoc.childLayoutString, this.props.childLayoutString),
childHideResizeHandles: this.childHideResizeHandles,
childHideDecorationTitle: this.childHideDecorationTitle,
- CollectionView: this,
};
return (
- <div className={'collectionView'} onContextMenu={this.onContextMenu} style={{ pointerEvents: this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? 'none' : undefined }}>
- {this.showIsTagged()}
+ <div
+ className="collectionView"
+ onContextMenu={this.onContextMenu}
+ style={{ pointerEvents: this.props.DocumentView?.()?.props.docViewPath().lastElement()?.rootDoc?._viewType === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? 'none' : undefined }}>
{this.renderSubView(this.collectionViewType, props)}
</div>
);
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f93810deb..4bbc3bb44 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -10,7 +10,6 @@ import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { FieldId } from '../../../fields/RefField';
import { listSpec } from '../../../fields/Schema';
-import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
import { DocServer } from '../../DocServer';
@@ -28,6 +27,7 @@ import { LightboxView } from '../LightboxView';
import { MainView } from '../MainView';
import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
import { DashFieldView } from '../nodes/formattedText/DashFieldView';
+import { KeyValueBox } from '../nodes/KeyValueBox';
import { PinProps, PresBox, PresMovement } from '../nodes/trails';
import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
import { CollectionDockingView } from './CollectionDockingView';
@@ -39,6 +39,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
interface TabDocViewProps {
documentId: FieldId;
+ keyValue?: boolean;
glContainer: any;
}
@observer
@@ -57,7 +58,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
}
@computed get tabBorderColor() {
const highlight = DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting);
- if (highlight?.highlightIndex >= Doc.DocBrushStatus.highlighted) return highlight.highlightColor;
+ if (highlight?.highlightIndex === Doc.DocBrushStatus.highlighted) return highlight.highlightColor;
return 'transparent';
}
@computed get tabColor() {
@@ -247,13 +248,13 @@ export class TabDocView extends React.Component<TabDocViewProps> {
alert('Cannot pin presentation document to itself');
return;
}
- const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false);
- const pinDoc = anchorDoc && anchorDoc !== doc ? Doc.MakeDelegate(anchorDoc) : Doc.MakeDelegate(doc);
+ const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps);
+ const pinDoc = Doc.MakeDelegate(anchorDoc && anchorDoc !== doc ? anchorDoc : doc);
pinDoc.presentationTargetDoc = anchorDoc ?? doc;
pinDoc.title = doc.title + ' - Slide';
pinDoc.data = new List<Doc>(); // the children of the alias' layout are the presentation slide children. the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
pinDoc.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom;
- pinDoc.presDuration = 2000;
+ pinDoc.presDuration = pinDoc.presDuration ?? 1000;
pinDoc.groupWithUp = false;
pinDoc.context = curPres;
// these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time
@@ -263,9 +264,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc.treeViewFieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
pinDoc.treeViewExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
- const presArray: Doc[] = PresBox.Instance?.sortArray();
- const size: number = PresBox.Instance?.selectedArray.size;
- const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
if (!pinProps?.audioRange && duration !== undefined) {
@@ -274,10 +272,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc.presStartTime = NumCast(doc.clipStart);
pinDoc.presEndTime = NumCast(doc.clipEnd, duration);
}
- PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, doc);
- pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)');
- Doc.AddDocToList(curPres, 'data', pinDoc, presSelected);
- //save position
if (pinProps?.activeFrame !== undefined) {
pinDoc.presActiveFrame = pinProps?.activeFrame;
pinDoc.title = doc.title + ' (move)';
@@ -294,6 +288,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc.presMovement = PresMovement.None;
}
if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
+ Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement());
PresBox.Instance?.clearSelectedArray();
pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array
});
@@ -305,7 +300,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []);
if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1);
CollectionDockingView.AddSplit(curPres, OpenWhereMod.right);
- setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), { willPan: true }, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things
+ setTimeout(() => DocumentManager.Instance.showDocument(docList.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things
}
setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
}
@@ -350,22 +345,31 @@ export class TabDocView extends React.Component<TabDocViewProps> {
// replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents,
// "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name,
// "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
- // inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack
+ // lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox
addDocTab = (doc: Doc, location: OpenWhere) => {
SelectionManager.DeselectAll();
const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':');
- const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
+ const keyValue = whereFields[1]?.includes('KeyValue');
+ const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none;
if (doc.dockingConfig) return DashboardView.openDashboard(doc);
// prettier-ignore
switch (whereFields[0]) {
- case OpenWhere.inPlace: // fall through to lightbox
- case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location);
+ case undefined:
+ case OpenWhere.lightbox: if (this.layoutDoc?._isLightbox) {
+ const lightboxView = !doc.annotationOn && DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined;
+ const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)];
+ if (lightboxView && (!data || data instanceof List)) {
+ lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)] = new List<Doc>([doc]);
+ return true;
+ }
+ }
+ return LightboxView.AddDocTab(doc, location);
case OpenWhere.dashboard: return DashboardView.openDashboard(doc);
case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc);
case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods);
- case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack);
- case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack);
- case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack);
+ case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, undefined, keyValue);
+ case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, undefined, keyValue);
+ case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue);
}
};
remDocTab = (doc: Doc | Doc[]) => {
@@ -380,13 +384,17 @@ export class TabDocView extends React.Component<TabDocViewProps> {
getCurrentFrame = () => {
return NumCast(Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null)._currentFrame);
};
+ static Activate = (tabDoc: Doc) => {
+ const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc);
+ tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
+ return tab !== undefined;
+ };
@action
focusFunc = (doc: Doc, options: DocFocusOptions) => {
- options?.afterFocus?.(false);
-
if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
}
+ return undefined;
};
active = () => this._isActive;
@observable _forceInvalidateScreenToLocal = 0;
@@ -413,12 +421,14 @@ export class TabDocView extends React.Component<TabDocViewProps> {
this._lastView = this._view;
})}
renderDepth={0}
+ LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined}
+ hideTitle={this.props.keyValue}
Document={this._document}
DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
onBrowseClick={MainView.Instance.exploreMode}
+ waitForDoubleClickToClick={MainView.Instance.waitForDoubleClick}
isContentActive={returnTrue}
+ isDocumentActive={returnFalse}
PanelWidth={this.PanelWidth}
PanelHeight={this.PanelHeight}
styleProvider={DefaultStyleProvider}
@@ -567,9 +577,6 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
<div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
<CollectionFreeFormView
Document={this.props.document}
- CollectionView={undefined}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
docViewPath={returnEmptyDoclist}
childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
noOverlay={true} // don't render overlay Docs since they won't scale
@@ -591,7 +598,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
ScreenToLocalTransform={Transform.Identity}
renderDepth={0}
whenChildContentsActiveChanged={emptyFunction}
- focus={DocUtils.DefaultFocus}
+ focus={emptyFunction}
styleProvider={TabMinimapView.miniStyleProvider}
addDocTab={this.props.addDocTab}
pinToPres={TabDocView.PinDoc}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 0d2968ba1..8fb610b87 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,20 +1,21 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
+import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
+import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -32,7 +33,6 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
import { CollectionView } from './CollectionView';
import './TreeView.scss';
import React = require('react');
-import { render } from 'react-dom';
export interface TreeViewProps {
treeView: CollectionTreeView;
@@ -42,13 +42,13 @@ export interface TreeViewProps {
prevSibling?: Doc;
document: Doc;
dataDoc?: Doc;
- containerCollection: Doc;
+ treeViewParent: Doc;
renderDepth: number;
dropAction: dropActionType;
addDocTab: (doc: Doc, where: OpenWhere) => boolean;
panelWidth: () => number;
panelHeight: () => number;
- addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean;
+ addDocument: (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => boolean;
removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined;
moveDocument: DragManager.MoveFunction;
isContentActive: (outsideReaction?: boolean) => boolean;
@@ -66,8 +66,8 @@ export interface TreeViewProps {
skipFields?: string[];
firstLevel: boolean;
// TODO: [AL] add these
- AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
- RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
hierarchyIndex?: number[];
}
@@ -143,7 +143,7 @@ export class TreeView extends React.Component<TreeViewProps> {
return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView;
}
@computed get MAX_EMBED_HEIGHT() {
- return NumCast(this.props.containerCollection.maxEmbedHeight, 200);
+ return NumCast(this.props.treeViewParent.maxEmbedHeight, 200);
}
@computed get dataDoc() {
return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym];
@@ -169,37 +169,22 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get selected() {
return SelectionManager.IsSelected(this._docRef);
}
- // SelectionManager.Views().lastElement()?.props.Document === this.props.document; }
-
- @observable _presTimer!: NodeJS.Timeout;
- @observable _presKeyEventsActive: boolean = false;
-
- @observable _selectedArray: ObservableMap = new ObservableMap<Doc, any>();
- // the selected item's index
- @computed get itemIndex() {
- return NumCast(this.doc._itemIndex);
- }
- // the item that's active
- @computed get activeItem() {
- return this.childDocs ? Cast(this.childDocs[NumCast(this.doc._itemIndex)], Doc, null) : undefined;
- }
- @computed get targetDoc() {
- return Cast(this.activeItem?.presentationTargetDoc, Doc, null);
- }
childDocList(field: string) {
const layout = Cast(Doc.LayoutField(this.doc), Doc, null);
- return (
- (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field
- (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields
- DocListCastOrNull(this.doc[field])
- ); // otherwise use the document's data field
+ return DocListCast(this.props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.doc[field])));
}
+ moving: boolean = false;
@undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
if (this.doc !== target && addDoc !== returnFalse) {
+ const canAdd1 = (this.props.parentTreeView as any).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this.props.parentTreeView?.doc.data)) instanceof ComputedField);
+
// bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse
- if (this.props.removeDoc?.(doc) === true) {
- return addDoc(doc);
+ if (canAdd1 && this.props.removeDoc?.(doc) === true) {
+ this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.moving = true);
+ const res = addDoc(doc);
+ this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.moving = false);
+ return res;
}
}
return false;
@@ -207,8 +192,9 @@ export class TreeView extends React.Component<TreeViewProps> {
@undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
this.props.treeView.props.select(false);
const ind = this.dataDoc[key].indexOf(doc);
+
const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
- res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.CollectionView)?.select(false);
+ res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.DocumentView?.())?.select(false);
return res;
};
@@ -319,7 +305,7 @@ export class TreeView extends React.Component<TreeViewProps> {
};
public static makeTextBullet() {
- const bullet = Docs.Create.TextDocument('-text-', {
+ const bullet = Docs.Create.TextDocument('', {
layout: CollectionView.LayoutString('data'),
title: '-title-',
treeViewExpandedViewLock: true,
@@ -340,7 +326,8 @@ export class TreeView extends React.Component<TreeViewProps> {
});
Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
Doc.GetProto(bullet).data = new List<Doc>([]);
- FormattedTextBox.SelectOnLoad = bullet[Id];
+ DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
+
return bullet;
}
@@ -372,26 +359,38 @@ export class TreeView extends React.Component<TreeViewProps> {
if (de.complete.linkDragData) {
const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
const destDoc = this.doc;
- DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, 'tree link', '');
+ DocUtils.MakeLink(sourceDoc, destDoc, { linkRelationship: 'tree link' });
e.stopPropagation();
}
const docDragData = de.complete.docDragData;
if (docDragData && pt[0] < rect.left + rect.width) {
if (docDragData.draggedDocuments[0] === this.doc) return true;
- if (this.dropDocuments(docDragData.droppedDocuments, before, inside, docDragData.dropAction, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document)) {
+ if (this.dropDocuments(docDragData.droppedDocuments, before, inside, docDragData.dropAction, docDragData.removeDocument, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document)) {
e.stopPropagation();
}
}
};
- dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) {
- const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
- const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes('add')) || forceAdd;
- const localAdd = (doc: Doc) => (Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false);
- const addDoc = !inside ? parentAddDoc : (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean);
+ dropping: boolean = false;
+ dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) {
+ const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, undefined, before);
+ const localAdd = (doc: Doc | Doc[]) => {
+ const innerAdd = (doc: Doc) => {
+ const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
+ const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
+ dataIsComputed && (doc.context = this.doc.context);
+ return added;
+ };
+ return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
+ };
+ const addDoc = inside ? localAdd : parentAddDoc;
const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument;
+ const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.freezeChildren).includes('add')) || forceAdd;
if (canAdd) {
- return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false));
+ this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
+ const res = UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false));
+ this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = false);
+ return res;
}
return false;
}
@@ -407,7 +406,7 @@ export class TreeView extends React.Component<TreeViewProps> {
getTransform = () => this.refTransform(this._tref.current);
embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1);
embeddedPanelHeight = () => {
- const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+ const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
return Math.min(
layoutDoc[HeightSym](),
this.MAX_EMBED_HEIGHT,
@@ -417,7 +416,7 @@ export class TreeView extends React.Component<TreeViewProps> {
return layoutDoc._fitWidth
? !Doc.NativeHeight(layoutDoc)
? NumCast(layoutDoc._height)
- : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)))
+ : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height)))
: (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym]();
})()
);
@@ -437,23 +436,27 @@ export class TreeView extends React.Component<TreeViewProps> {
const expandedWidth = () => this.props.panelWidth() - leftOffset.width;
if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) {
const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
- const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
- const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
- added && (doc.context = this.doc.context);
- return added;
+ const moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc);
+ const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => {
+ const innerAdd = (doc: Doc) => {
+ const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
+ const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
+ dataIsComputed && (doc.context = this.doc.context);
+ return added;
+ };
+ return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
};
- const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
contentElement = TreeView.GetChildElements(
contents instanceof Doc ? [contents] : DocListCast(contents),
this.props.treeView,
this,
doc,
undefined,
- this.props.containerCollection,
+ this.props.treeViewParent,
this.props.prevSibling,
addDoc,
remDoc,
- this.move,
+ moveDoc,
this.props.dropAction,
this.props.addDocTab,
this.titleStyleProvider,
@@ -533,6 +536,7 @@ export class TreeView extends React.Component<TreeViewProps> {
);
const key = (expandKey === 'annotations' ? `${this.fieldKey}-` : '') + expandKey;
const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
+ const moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc);
const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
// if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't),
// then the modification would be done here
@@ -543,8 +547,10 @@ export class TreeView extends React.Component<TreeViewProps> {
docs.push(doc);
docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => (d.zIndex = i));
}
- const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
- added && (doc.context = this.doc.context);
+ const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
+ const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
+ !dataIsComputed && added && (doc.context = this.doc.context);
+
return added;
};
const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
@@ -567,6 +573,7 @@ export class TreeView extends React.Component<TreeViewProps> {
</div>
)}
<ul
+ style={{ cursor: 'inherit' }}
key={expandKey + 'more'}
title="click to change sort order"
className={''} //this.doc.treeViewHideTitle ? 'no-indent' : ''}
@@ -589,11 +596,11 @@ export class TreeView extends React.Component<TreeViewProps> {
this,
this.layoutDoc,
this.dataDoc,
- this.props.containerCollection,
+ this.props.treeViewParent,
this.props.prevSibling,
addDoc,
remDoc,
- this.move,
+ moveDoc,
StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType,
this.props.addDocTab,
this.titleStyleProvider,
@@ -623,7 +630,7 @@ export class TreeView extends React.Component<TreeViewProps> {
);
} else if (this.treeViewExpandedView === 'fields') {
return (
- <ul key={this.doc[Id] + this.doc.title}>
+ <ul key={this.doc[Id] + this.doc.title} style={{ cursor: 'inherit' }}>
<div>{this.expandedField}</div>
</ul>
);
@@ -649,7 +656,7 @@ export class TreeView extends React.Component<TreeViewProps> {
this.onCheckedClick?.script.run(
{
this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
- heading: this.props.containerCollection.title,
+ heading: this.props.treeViewParent.title,
checked: this.doc.treeViewChecked === 'check' ? 'x' : this.doc.treeViewChecked === 'x' ? 'remove' : 'check',
containingTreeView: this.props.treeView.props.Document,
},
@@ -700,7 +707,7 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get validExpandViewTypes() {
const annos = () => (DocListCast(this.doc[this.fieldKey + '-annotations']).length && !this.props.treeView.dashboardMode ? 'annotations' : '');
- const links = () => (DocListCast(this.doc.links).length && !this.props.treeView.dashboardMode ? 'links' : '');
+ const links = () => (LinkManager.Links(this.doc).length && !this.props.treeView.dashboardMode ? 'links' : '');
const data = () => (this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : '');
const aliases = () => (this.props.treeView.dashboardMode ? '' : 'aliases');
const fields = () => (Doc.noviceMode ? '' : 'fields');
@@ -717,9 +724,11 @@ export class TreeView extends React.Component<TreeViewProps> {
};
@observable headerEleWidth = 0;
- @computed get headerElements() {
+ @computed get titleButtons() {
+ const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations);
return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : (
<>
+ {customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */}
{this.doc.hideContextMenu ? null : (
<FontAwesomeIcon
title="context menu"
@@ -750,7 +759,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' };
const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'Delete' };
const folderOp = this.childDocs?.length ? [makeFolder] : [];
- const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: 'copy', label: 'Open Alias' };
+ const openAlias = { script: ScriptField.MakeFunction(`openDoc(getAlias(self), ${OpenWhere.addRight})`)!, icon: 'copy', label: 'Open Alias' };
const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' };
return [
...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
@@ -772,9 +781,7 @@ export class TreeView extends React.Component<TreeViewProps> {
return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
};
- onChildClick = () => {
- return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!);
- };
+ onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!);
onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null);
@@ -897,7 +904,9 @@ export class TreeView extends React.Component<TreeViewProps> {
hideDecorationTitle={this.props.treeView.outlineMode}
hideResizeHandles={this.props.treeView.outlineMode}
styleProvider={this.titleStyleProvider}
- docViewPath={returnEmptyDoclist}
+ enableDragWhenActive={true}
+ onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
+ docViewPath={this.props.treeView.props.docViewPath}
treeViewDoc={this.props.treeView.props.Document}
addDocument={undefined}
addDocTab={this.props.addDocTab}
@@ -928,12 +937,8 @@ export class TreeView extends React.Component<TreeViewProps> {
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={this.props.treeView.props.CollectionView}
- ContainingCollectionDoc={this.props.treeView.props.Document}
/>
);
-
- const buttons = this.props.styleProvider?.(this.doc, { ...this.props.treeView.props, ContainingCollectionDoc: this.props.parentTreeView?.doc }, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ':afterHeader' : ''));
return (
<>
<div
@@ -949,8 +954,7 @@ export class TreeView extends React.Component<TreeViewProps> {
{view}
</div>
<div className="treeView-rightButtons" ref={action((r: any) => r && (this.headerEleWidth = r.getBoundingClientRect().width))}>
- {buttons} {/* hide and lock buttons */}
- {this.headerElements}
+ {this.titleButtons}
</div>
</>
);
@@ -1008,8 +1012,6 @@ export class TreeView extends React.Component<TreeViewProps> {
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.containerCollection}
- ContainingCollectionView={undefined}
addDocument={this.props.addDocument}
moveDocument={this.move}
removeDocument={this.props.removeDoc}
@@ -1028,7 +1030,9 @@ export class TreeView extends React.Component<TreeViewProps> {
// renders the text version of a document as the header. This is used in the file system mode and in other vanilla tree views.
@computed get renderTitleAsHeader() {
- return (
+ return this.props.treeView.Document.treeViewHideUnrendered && this.doc.unrendered && !this.doc.treeViewFieldKey ? (
+ <div></div>
+ ) : (
<>
{this.renderBullet}
{this.renderTitle}
@@ -1062,7 +1066,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const before = pt[1] < rect.top + rect.height / 2;
const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
- const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, false));
+ const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false));
};
render() {
@@ -1120,7 +1124,7 @@ export class TreeView extends React.Component<TreeViewProps> {
childDocs: Doc[],
treeView: CollectionTreeView,
parentTreeView: CollectionTreeView | TreeView | undefined,
- containerCollection: Doc,
+ treeViewParent: Doc,
dataDoc: Doc | undefined,
parentCollectionDoc: Doc | undefined,
containerPrevSibling: Doc | undefined,
@@ -1146,24 +1150,24 @@ export class TreeView extends React.Component<TreeViewProps> {
unobserveHeight: (ref: any) => void,
contextMenuItems: { script: ScriptField; filter: ScriptField; label: string; icon: string }[],
// TODO: [AL] add these
- AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
- RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => void,
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => void,
hierarchyIndex?: number[],
renderCount?: number
) {
- const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField);
+ const viewSpecScript = Cast(treeViewParent.viewSpecScript, ScriptField);
if (viewSpecScript) {
childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
}
- const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None));
+ const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeViewSortCriterion, TreeSort.None));
const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1);
const treeViewRefs = new Map<Doc, TreeView | undefined>();
return docs
.filter(child => child instanceof Doc)
.map((child, i) => {
if (renderCount && i > renderCount) return null;
- const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child);
+ const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child);
if (!pair.layout || pair.data instanceof Promise) {
return null;
}
@@ -1181,11 +1185,8 @@ export class TreeView extends React.Component<TreeViewProps> {
}
};
const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
- const outdent =
- parentCollectionDoc?._viewType !== CollectionViewType.Tree
- ? undefined
- : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
- const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
+ const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
+ const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
const childLayout = Doc.Layout(pair.layout);
const rowHeight = () => {
const aspect = Doc.NativeAspect(childLayout);
@@ -1197,7 +1198,7 @@ export class TreeView extends React.Component<TreeViewProps> {
ref={r => treeViewRefs.set(child, r ? r : undefined)}
document={pair.layout}
dataDoc={pair.data}
- containerCollection={containerCollection}
+ treeViewParent={treeViewParent}
prevSibling={docs[i]}
// TODO: [AL] add these
hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined}
@@ -1209,7 +1210,7 @@ export class TreeView extends React.Component<TreeViewProps> {
onCheckedClick={onCheckedClick}
onChildClick={onChildClick}
renderDepth={renderDepth}
- removeDoc={StrCast(containerCollection.freezeChildren).includes('remove') ? undefined : remove}
+ removeDoc={StrCast(treeViewParent.freezeChildren).includes('remove') ? undefined : remove}
addDocument={addDocument}
styleProvider={styleProvider}
panelWidth={rowWidth}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 7dd9cdb8b..81b0c4d8a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -14,6 +14,7 @@ export interface ViewDefBounds {
x: number;
y: number;
z?: number;
+ rotation?: number;
text?: string;
zIndex?: number;
width?: number;
@@ -31,6 +32,7 @@ export interface PoolData {
x: number;
y: number;
z?: number;
+ rotation?: number;
zIndex?: number;
width?: number;
height?: number;
@@ -114,6 +116,8 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
zIndex: NumCast(layout.zIndex),
pair: { layout, data },
replica: '',
+ color: 'white',
+ backgroundColor: 'white',
});
});
const divider = { type: 'div', color: 'transparent', x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined };
@@ -406,7 +410,7 @@ function normalizeResults(
.map(ele => {
const newPosRaw = ele[1];
if (newPosRaw) {
- const newPos = {
+ const newPos: PoolData = {
x: newPosRaw.x * scale,
y: newPosRaw.y * scale,
z: newPosRaw.z,
@@ -415,6 +419,9 @@ function normalizeResults(
zIndex: newPosRaw.zIndex,
width: (newPosRaw.width || 0) * scale,
height: newPosRaw.height! * scale,
+ backgroundColor: newPosRaw.backgroundColor,
+ opacity: newPosRaw.opacity,
+ color: newPosRaw.color,
pair: ele[1].pair,
};
poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7d176f426..33fc2ddf3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,14 +1,13 @@
import { Bezier } from 'bezier-js';
import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../../fields/Doc';
+import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
-import { ObjectField } from '../../../../fields/ObjectField';
import { RichTextField } from '../../../../fields/RichTextField';
import { listSpec } from '../../../../fields/Schema';
import { ScriptField } from '../../../../fields/ScriptField';
@@ -16,13 +15,12 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro
import { ImageField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnTransparent, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager, dropActionType } from '../../../util/DragManager';
-import { HistoryUtil } from '../../../util/History';
import { InteractionUtils } from '../../../util/InteractionUtils';
import { ReplayMovements } from '../../../util/ReplayMovements';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -34,15 +32,15 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss';
import { Timeline } from '../../animationtimeline/Timeline';
import { ContextMenu } from '../../ContextMenu';
+import { DocumentDecorations } from '../../DocumentDecorations';
import { GestureOverlay } from '../../GestureOverlay';
import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
import { LightboxView } from '../../LightboxView';
import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
-import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
import { FieldViewProps } from '../../nodes/FieldView';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { PresBox } from '../../nodes/trails/PresBox';
-import { VideoBox } from '../../nodes/VideoBox';
+import { PinProps, PresBox } from '../../nodes/trails/PresBox';
import { CreateImage } from '../../nodes/WebBoxRenderer';
import { StyleProp } from '../../StyleProvider';
import { CollectionSubView } from '../CollectionSubView';
@@ -53,14 +51,16 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso
import './CollectionFreeFormView.scss';
import { MarqueeView } from './MarqueeView';
import React = require('react');
-import { DocumentDecorations } from '../../DocumentDecorations';
-import { PresEffect } from '../../nodes/trails';
export type collectionFreeformViewProps = {
+ noPointerWheel?: () => boolean; // turn off pointerwheel interactions (see PDFViewer)
+ NativeWidth?: () => number;
+ NativeHeight?: () => number;
+ originTopLeft?: boolean;
annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
childPointerEvents?: string;
- scaleField?: string;
+ viewField?: string;
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
engineProps?: any;
getScrollHeight?: () => number | undefined;
@@ -78,7 +78,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable
public static ShowPresPaths = false;
- private _lastNudge: any;
+ private _panZoomTransitionTimer: any;
private _lastX: number = 0;
private _lastY: number = 0;
private _downX: number = 0;
@@ -98,14 +98,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _brushtimer: any;
private _brushtimer1: any;
- // private isWritingMode: boolean = true;
- // private writingModeDocs: Doc[] = [];
-
private get isAnnotationOverlay() {
return this.props.isAnnotationOverlay;
}
- private get scaleFieldKey() {
- return this.props.scaleField || '_viewScale';
+ public get scaleFieldKey() {
+ return this.props.viewField ? this.props.viewField + '-viewScale' : '_viewScale';
+ }
+ private get panXFieldKey() {
+ return this.props.viewField ? this.props.viewField + '-panX' : '_panX';
+ }
+ private get panYFieldKey() {
+ return this.props.viewField ? this.props.viewField + '-panY' : '_panY';
}
private get borderWidth() {
return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
@@ -115,7 +118,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _panZoomTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0
@observable _hLines: number[] | undefined;
@observable _vLines: number[] | undefined;
- @observable _firstRender = true; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown.
+ @observable _firstRender = false; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown. could be used for performance improvement
@observable _showAnimTimeline = false;
@observable _clusterSets: Doc[][] = [];
@observable _deleteList: DocumentView[] = [];
@@ -127,7 +130,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
constructor(props: any) {
super(props);
- this.props.setContentView?.(this);
}
@computed get views() {
@@ -143,7 +145,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
}
@computed get fitContentsToBox() {
- return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox || this.Document.isGroup) && !this.isAnnotationOverlay;
+ return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox) && !this.isAnnotationOverlay;
}
@computed get contentBounds() {
const cb = Cast(this.rootDoc.contentBounds, listSpec('number'));
@@ -157,21 +159,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
);
}
@computed get nativeWidth() {
- return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ return this.props.NativeWidth?.() || (this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
}
@computed get nativeHeight() {
- return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ return this.props.NativeHeight?.() || (this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
}
@computed get cachedCenteringShiftX(): number {
const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
- return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedCenteringShiftY(): number {
const dv = this.props.DocumentView?.();
const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
// if freeform has a native aspect, then the panel height needs to be adjusted to match it
const aspect = dv?.nativeWidth && dv?.nativeHeight && !dv.layoutDoc.fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
- return this.props.isAnnotationOverlay ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedGetLocalTransform(): Transform {
return Transform.Identity()
@@ -185,8 +187,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return this.getContainerTransform().translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
}
- _keyTimer: NodeJS.Timeout | undefined;
-
public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
if (timer) clearTimeout(timer);
return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true);
@@ -212,6 +212,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return newTimer;
}
+ _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation.
changeKeyFrame = (back = false) => {
const currentFrame = Cast(this.Document._currentFrame, 'number', null);
if (currentFrame === undefined) {
@@ -245,13 +246,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
reverseNativeScaling = () => (this.fitContentsToBox ? true : false);
// panx, pany, zoomscale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
// 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._panX, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1));
- panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1));
+ panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1));
+ panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1));
zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
contentTransform = () =>
- !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1
- ? ''
- : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+ this.props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
getTransform = () => this.cachedGetTransform.copy();
getLocalTransform = () => this.cachedGetLocalTransform.copy();
getContainerTransform = () => this.cachedGetContainerTransform.copy();
@@ -263,7 +262,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
- docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true));
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).map(dv => dv && SelectionManager.SelectView(dv, true));
};
addDocument = (newBox: Doc | Doc[]) => {
let retVal = false;
@@ -301,6 +300,39 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return dispTime === -1 || curTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime);
}
+ groupFocus = (anchor: Doc, options: DocFocusOptions) => {
+ options.docTransform = new Transform(-NumCast(this.rootDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.rootDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
+ const res = this.props.focus(this.rootDoc, options);
+ options.docTransform = undefined;
+ return res;
+ };
+
+ focus = (anchor: Doc, options: DocFocusOptions) => {
+ const xfToCollection = options?.docTransform ?? Transform.Identity();
+ const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
+ const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+ const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale ?? 0.75 : undefined);
+
+ // focus on the document in the collection
+ const didMove = !cantTransform && !anchor.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
+ if (didMove) options.didMove = true;
+ // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
+ if (didMove) {
+ const focusTime = options?.instant ? 0 : options.zoomTime ?? 500;
+ (options.zoomScale ?? options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale);
+ this.setPan(panX, panY, focusTime, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+ return focusTime;
+ }
+ };
+
+ getView = async (doc: Doc): Promise<Opt<DocumentView>> => {
+ return new Promise<Opt<DocumentView>>(res => {
+ doc.hidden && (doc.hidden = false);
+ const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
+ findDoc(dv => res(dv));
+ });
+ };
+
@action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
if (!de.embedKey && !this.ChildDrag && this.rootDoc._isGroup) return false;
@@ -317,6 +349,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1));
const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
+
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
const d = docDragData.droppedDocuments[i];
const layoutDoc = Doc.Layout(d);
@@ -338,6 +371,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
(d._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
+ if (this.layoutDoc._autoArrange || de.metaKey) {
+ const sorted = this.childLayoutPairs.slice().sort((a, b) => NumCast(a.layout.y) - NumCast(b.layout.y));
+ sorted.splice(
+ sorted.findIndex(pair => pair.layout === refDoc),
+ 1
+ );
+ if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) {
+ const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height) / 2;
+ const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0;
+ const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0;
+
+ let lastx = NumCast(refDoc.x);
+ let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height));
+ runInAction(() =>
+ sorted.slice(1).forEach((pair, i) => {
+ lastx = pair.layout.x = lastx + deltax;
+ lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height));
+ })
+ );
+ }
+ }
+
(docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
return true;
}
@@ -365,7 +420,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' });
this.props.addDocument?.(source);
- de.complete.linkDocument = DocUtils.MakeLink({ doc: linkDragData.linkSourceGetAnchor() }, { doc: source }, 'annotated by:annotation of', ''); // TODODO this is where in text links get passed
+ de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { linkRelationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
}
e.stopPropagation(); // do nothing if link is dropped into any freeform view parent of dragged document
return true;
@@ -407,7 +462,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0);
if (ptsParent) {
const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster);
- const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!);
+ const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!);
const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'alias' : undefined);
de.moveDocument = this.props.moveDocument;
@@ -500,7 +555,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._clusterSets.push([doc]);
} else if (this._clusterSets.length) {
for (let i = this._clusterSets.length; i <= doc.cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
- this._clusterSets[doc.cluster].push(doc);
+ this._clusterSets[doc.cluster ?? 0].push(doc);
}
}
}
@@ -523,6 +578,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
break;
+ case StyleProp.FillColor:
+ if (doc && this.Document._currentFrame !== undefined) {
+ return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor;
+ }
}
return styleProp;
};
@@ -559,14 +618,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) &&
!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)
) {
+ // prettier-ignore
switch (Doc.ActiveTool) {
- case InkTool.Highlighter:
- break;
- // TODO: nda - this where we want to create the new "writingDoc" collection that we add strokes to
- case InkTool.Write:
- break;
- case InkTool.Pen:
- break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
+ case InkTool.Highlighter: break;
+ case InkTool.Write: break;
+ case InkTool.Pen: break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
case InkTool.Eraser:
this._batch = UndoManager.StartBatch('collectionErase');
setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
@@ -631,8 +687,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
ActiveIsInkMask(),
{
title: 'ink stroke',
- x: B.x - ActiveInkWidth() / 2,
- y: B.y - ActiveInkWidth() / 2,
+ x: B.x - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2,
+ y: B.y - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2,
_width: B.width + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
_height: B.height + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
}
@@ -747,7 +803,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
PresBox.Instance?.pauseAutoPres();
const dx = e.deltaX;
const dy = e.deltaY;
- this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
+ this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
};
@action
@@ -755,7 +811,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
PresBox.Instance?.pauseAutoPres();
this.props.DocumentView?.().clearViewTransition();
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
+ this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
this._lastX = e.clientX;
this._lastY = e.clientY;
};
@@ -796,8 +852,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return false;
if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
Doc.ActiveTool = InkTool.None;
- if (this.props.isContentActive(true)) e.stopPropagation();
- } else if (!e.cancelBubble) {
+ } else {
if (this.tryDragCluster(e, this._hitCluster)) {
return true;
}
@@ -815,7 +870,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
return this.childDocs
- .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView))
+ .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.()))
.filter(inkView => inkView?.ComponentView instanceof InkingStroke)
.map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
.filter(
@@ -909,7 +964,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.childDocs
.filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
.forEach(doc => {
- const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)?.ComponentView as InkingStroke;
+ const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())?.ComponentView as InkingStroke;
const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
@@ -929,96 +984,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return tVals;
};
- handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
- if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- if (myTouches[0]) {
- if (Doc.ActiveTool === InkTool.None) {
- if (this.tryDragCluster(e, this._hitCluster)) {
- e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
- e.preventDefault();
- document.removeEventListener('pointermove', this.onPointerMove);
- return;
- }
- // TODO: nda - this allows us to pan collections with finger -> only want to do this when collection is selected'
- this.pan(myTouches[0]);
- }
- }
- // e.stopPropagation();
- e.preventDefault();
- }
- };
-
- handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
- // pinch zooming
- if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- const pt1 = myTouches[0];
- const pt2 = myTouches[1];
-
- if (this.prevPoints.size === 2) {
- const oldPoint1 = this.prevPoints.get(pt1.identifier);
- const oldPoint2 = this.prevPoints.get(pt2.identifier);
- if (oldPoint1 && oldPoint2) {
- const dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2);
-
- // if zooming, zoom
- if (dir !== 0) {
- const d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2));
- const d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2));
- const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
- const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-
- // calculate the raw delta value
- const rawDelta = dir * (d1 + d2);
-
- // this floors and ceils the delta value to prevent jitteriness
- const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8);
- this.zoom(centerX, centerY, delta * window.devicePixelRatio);
- this.prevPoints.set(pt1.identifier, pt1);
- this.prevPoints.set(pt2.identifier, pt2);
- }
- // this is not zooming. derive some form of panning from it.
- else {
- // use the centerx and centery as the "new mouse position"
- const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
- const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
- // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
-
- this._lastX = centerX;
- this._lastY = centerY;
- }
- }
- }
- // e.stopPropagation();
- e.preventDefault();
- }
- };
-
- @action
- handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- if (this.props.isContentActive(true)) {
- // const pt1: React.Touch | null = e.targetTouches.item(0);
- // const pt2: React.Touch | null = e.targetTouches.item(1);
- // // if (!pt1 || !pt2) return;
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- const pt1 = myTouches[0];
- const pt2 = myTouches[1];
- if (pt1 && pt2) {
- const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
- const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
- this._lastX = centerX;
- this._lastY = centerY;
-
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
- e.stopPropagation();
- }
- }
- };
-
cleanUpInteractions = () => {
this.removeMoveListeners();
this.removeEndListeners();
@@ -1045,13 +1010,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
- this.setPan(-localTransform.TranslateX / safeScale, NumCast(this.props.Document.scrollTop) * safeScale || -localTransform.TranslateY / safeScale);
+ this.setPan(-localTransform.TranslateX / safeScale, (this.props.originTopLeft ? undefined : NumCast(this.props.Document.scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
}
};
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (this.Document._isGroup) return; // group style collections neither pan nor zoom
+ if (this.props.noPointerWheel?.() || this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
PresBox.Instance?.pauseAutoPres();
if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
e.stopPropagation();
@@ -1061,7 +1026,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// if ctrl is selected then zoom
if (e.ctrlKey) {
if (this.props.isContentActive(true)) {
- !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+ this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
}
} // otherwise pan
else if (this.props.isContentActive(true)) {
@@ -1105,20 +1070,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) },
}),
{
- xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
- yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ xrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ yrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
}
);
- const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
- if (ranges.xrange.min >= panX + panelDim[0] / 2) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
- else if (ranges.xrange.max <= panX - panelDim[0] / 2) panX = ranges.xrange.min - panelDim[0] / 2;
- if (ranges.yrange.min >= panY + panelDim[1] / 2) panY = ranges.yrange.max + panelDim[1] / 2;
- else if (ranges.yrange.max <= panY - panelDim[1] / 2) panY = ranges.yrange.min - panelDim[1] / 2;
+ const panelWidMax = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+ const panelWidMin = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
+ const panelHgtMax = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+ const panelHgtMin = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
+ if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this.props.originTopLeft ? 0 : panelWidMax / 2);
+ else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this.props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
+ if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this.props.originTopLeft ? 0 : panelHgtMax / 2);
+ else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this.props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
}
}
if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(Doc.MyOverlayDocs?.data).includes(this.Document)) {
- this._panZoomTransition = panTime;
+ this.setPanZoomTransition(panTime);
const scale = this.getLocalTransform().inverse().Scale;
const minScale = NumCast(this.rootDoc._viewScaleMin, 1);
const minPanX = NumCast(this.rootDoc._panXMin, 0);
@@ -1144,20 +1112,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}, 10);
newPanY = minPanY;
}
- !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX);
- !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY);
+ !this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX);
+ !this.Document._horizontalScroll && (this.Document[this.panYFieldKey] = this.isAnnotationOverlay ? newPanY : panY);
}
}
@action
nudge = (x: number, y: number, nudgeTime: number = 500) => {
- if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) {
- // bcz: this isn't ideal, but want to try it out...
- this.setPan(NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), nudgeTime, true);
- this._lastNudge && clearTimeout(this._lastNudge);
- this._lastNudge = setTimeout(
- action(() => (this._panZoomTransition = 0)),
- nudgeTime
+ const collectionDoc = this.props.docViewPath().lastElement().rootDoc;
+ if (collectionDoc?._viewType !== CollectionViewType.Freeform || collectionDoc._panX !== undefined) {
+ this.setPan(
+ NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
+ NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
+ nudgeTime,
+ true
);
return true;
}
@@ -1173,111 +1141,52 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
} else {
const docs = this.childLayoutPairs.map(pair => pair.layout).slice();
docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
- let zlast = docs.length ? Math.max(docs.length, NumCast(docs[docs.length - 1].zIndex)) : 1;
- if (zlast - docs.length > 100) {
- for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
- zlast = docs.length + 1;
+ let zlast = docs.length ? Math.max(docs.length, NumCast(docs.lastElement().zIndex)) : 1;
+ if (docs.lastElement() !== doc) {
+ if (zlast - docs.length > 100) {
+ for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
+ zlast = docs.length + 1;
+ }
+ doc.zIndex = zlast + 1;
}
- doc.zIndex = zlast + 1;
}
};
@action
+ setPanZoomTransition = (transitionTime: number) => {
+ this._panZoomTransition = transitionTime;
+ this._panZoomTransitionTimer && clearTimeout(this._panZoomTransitionTimer);
+ this._panZoomTransitionTimer = setTimeout(
+ action(() => (this._panZoomTransition = 0)),
+ transitionTime
+ );
+ };
+
+ @action
zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
if (this.Document._isGroup) return;
- this._panZoomTransition = transitionTime;
+ this.setPanZoomTransition(transitionTime);
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
this.layoutDoc[this.scaleFieldKey] = scale;
const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
- this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0];
- this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
- return new Promise<number>(res => setTimeout(() => res(runInAction(() => (this._panZoomTransition = 0))), this._panZoomTransition)); // set transition to be smooth, then reset
+ this.layoutDoc[this.panXFieldKey] = NumCast(this.layoutDoc[this.panXFieldKey]) - newpan[0];
+ this.layoutDoc[this.panYFieldKey] = NumCast(this.layoutDoc[this.panYFieldKey]) - newpan[1];
}
- _focusCount = 0;
- focusDocument = (doc: Doc, options: DocFocusOptions) => {
- const state = HistoryUtil.getState();
-
- // TODO This technically isn't correct if type !== "doc", as
- // currently nothing is done, but we should probably push a new state
- if (state.type === 'doc' && this.Document._panX !== undefined && this.Document._panY !== undefined) {
- const init = state.initializers![this.Document[Id]];
- if (!init) {
- state.initializers![this.Document[Id]] = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY) };
- HistoryUtil.pushState(state);
- } else if (init.panX !== this.Document._panX || init.panY !== this.Document._panY) {
- init.panX = NumCast(this.Document._panX);
- init.panY = NumCast(this.Document._panY);
- HistoryUtil.pushState(state);
- }
- }
- // if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
- // SelectionManager.DeselectAll();
- // }
- if (this.props.getScrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined || doc.type === DocumentType.MARKER) {
- this.props.focus(doc, options);
- } else {
- const xfToCollection = options?.docTransform ?? Transform.Identity();
- const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willPanZoom ? this.Document[this.scaleFieldKey] : undefined };
- const newState = HistoryUtil.getState();
- const cantTransform = (this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc;
- const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willPanZoom) ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willPanZoom ? options?.zoomScale || 0.75 : undefined);
- if (!cantTransform) {
- // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
- newState.initializers![this.Document[Id]] = { panX, panY };
- HistoryUtil.pushState(newState);
- }
- // focus on the document in the collection
- const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
- const focusSpeed = options?.instant ? 0 : didMove || DocCast(options.effect)?.presEffect !== PresEffect.None ? options.zoomTime ?? 500 : 0;
- // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
- if (didMove) {
- options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale);
- this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
- }
-
- const focusCount = ++this._focusCount;
- const startTime = Date.now();
- // focus on this collection within its parent view. the parent view after focusing determines whether to reset the view change within the collection
- const endFocus = async (moved: boolean) => {
- doc.hidden && Doc.UnHighlightDoc(doc);
- const resetView = options?.afterFocus ? await options?.afterFocus(moved) : ViewAdjustment.doNothing;
- if (resetView) {
- const restoreState = savedState;
- if (typeof restoreState !== 'boolean') {
- this.Document._panX = restoreState.panX;
- this.Document._panY = restoreState.panY;
- this.Document[this.scaleFieldKey] = restoreState.scale;
- }
- }
- this._focusCount === focusCount && didMove && runInAction(() => (this._panZoomTransition = 0));
- return resetView;
- };
- const xf = !cantTransform
- ? Transform.Identity()
- : this.props.isAnnotationOverlay
- ? new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc))
- : new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1);
-
- this.props.focus(!cantTransform ? this.rootDoc : doc, {
- ...options,
- docTransform: xf,
- afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))),
- });
- }
- };
-
calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
const layoutdoc = Doc.Layout(doc);
const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]());
const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
- if (scale) {
+ if (scale !== undefined) {
const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
- const newScale = Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
+ const newScale =
+ scale === 0
+ ? NumCast(this.layoutDoc[this.scaleFieldKey])
+ : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
return {
panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
@@ -1289,8 +1198,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const panelHeight = this.props.isAnnotationOverlay ? this.nativeHeight : this.props.PanelHeight();
const pw = panelWidth / NumCast(this.layoutDoc._viewScale, 1);
const ph = panelHeight / NumCast(this.layoutDoc._viewScale, 1);
- const cx = NumCast(this.layoutDoc._panX) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
- const cy = NumCast(this.layoutDoc._panY) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
+ const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
+ const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
const maxYShift = Math.max(0, screen.bot - screen.top - (bounds.bot - bounds.top));
const phborder = bounds.top < screen.top || bounds.bot > screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0;
@@ -1302,8 +1211,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
}
return {
- panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panX) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
- panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panY) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
+ panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
+ panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
};
};
@@ -1332,10 +1241,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
pointerEvents = () => {
const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
- const pointerEvents =
- this.props.isContentActive() === false || DocumentDecorations.Instance.Interacting
- ? 'none'
- : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
+ const pointerEvents = DocumentDecorations.Instance.Interacting
+ ? 'none'
+ : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
return pointerEvents;
};
getChildDocView(entry: PoolData) {
@@ -1350,12 +1258,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
replica={entry.replica}
suppressSetHeight={this.layoutEngine ? true : false}
renderCutoffProvider={this.renderCutoffProvider}
- ContainingCollectionView={this.props.CollectionView}
- ContainingCollectionDoc={this.props.Document}
CollectionFreeFormView={this}
LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
rootSelected={childData ? this.rootSelected : returnFalse}
+ waitForDoubleClickToClick={this.props.waitForDoubleClickToClick}
onClick={this.onChildClickHandler}
onKey={this.onKeyDown}
onDoubleClick={this.onChildDoubleClickHandler}
@@ -1368,7 +1275,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
searchFilterDocs={this.searchFilterDocs}
isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
isContentActive={emptyFunction}
- focus={this.focusDocument}
+ focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
addDocTab={this.addDocTab}
addDocument={this.props.addDocument}
removeDocument={this.props.removeDocument}
@@ -1386,19 +1293,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
dontScaleFilter={this.props.dontScaleFilter}
dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
pointerEvents={this.pointerEvents}
- rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
+ //rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
//fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
/>
);
}
addDocTab = action((doc: Doc, where: OpenWhere) => {
+ if (this.props.isAnnotationOverlay) return this.props.addDocTab(doc, where);
switch (where) {
case OpenWhere.inParent:
return this.props.addDocument?.(doc) || false;
case OpenWhere.inParentFromScreen:
const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.context);
return (
- (this.props.addDocument?.(
+ (this.addDocument?.(
(doc instanceof Doc ? [doc] : doc).map(doc => {
const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
doc.x = pt[0];
@@ -1409,9 +1317,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
(!docContext || this.props.removeDocument?.(docContext))) ||
false
);
- case OpenWhere.inPlace:
- if (this.layoutDoc.isInPlaceContainer) {
- this.dataDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc);
+ case undefined:
+ case OpenWhere.lightbox:
+ if (this.layoutDoc._isLightbox) {
+ // _isLightbox docs have a script that will unset this overlay onClick
+ this.layoutDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc);
return true;
}
}
@@ -1425,17 +1335,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
const { z, zIndex } = childDoc;
const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
- const { x, y, opacity } = layoutFrameNumber === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
+ const { x, y, _width, _height, opacity, _rotation } =
+ layoutFrameNumber === undefined
+ ? { _width: Cast(childDocLayout._width, 'number'), _height: Cast(childDocLayout._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() }
+ : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
return {
x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
z: Cast(z, 'number'),
+ rotation: Cast(_rotation, 'number'),
color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor),
opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
zIndex: Cast(zIndex, 'number'),
- width: Cast(childDocLayout._width, 'number'),
- height: Cast(childDocLayout._height, 'number'),
+ width: _width,
+ height: _height,
transition: StrCast(childDocLayout.dataTransition),
pair: params.pair,
replica: '',
@@ -1556,6 +1470,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
newPos.x !== lastPos.x ||
newPos.y !== lastPos.y ||
newPos.z !== lastPos.z ||
+ newPos.rotation !== lastPos.rotation ||
newPos.zIndex !== lastPos.zIndex ||
newPos.transition !== lastPos.transition
) {
@@ -1583,7 +1498,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) {
// don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
- if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
+ if (this.props.viewField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
else this.props.Document[this.scaleFieldKey] = Math.max(1, this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey]));
}
@@ -1591,38 +1506,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return elements;
}
- @action
- scrollFocus = (anchor: Doc, options: DocFocusOptions) => {
- const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
- if (options.preview) {
- this._focusFilters = StrListCast(anchor.presPinDocFilters);
- this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters);
- return undefined;
- }
- return PresBox.restoreTargetDocView(
- this.props.DocumentView?.(), //
- { pinDocLayout: BoolCast(anchor.presPinLayout) },
- anchor,
- focusSpeed,
- {
- dataview: anchor.presData ? true : false,
- pannable: anchor.presPinData ? true : false,
- viewType: anchor.presPinViewType ? true : false,
- filters: anchor.presPinDocFilters || anchor.presPinDocRangeFilters ? true : false,
- }
- )
- ? focusSpeed
- : undefined;
- }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
-
- getAnchor = (addAsAnnotation: boolean) => {
- if (this.props.Document.annotationOn) {
- return this.rootDoc;
- }
-
+ 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.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), presTransition: 500, annotationOn: this.rootDoc });
- PresBox.pinDocView(anchor, { pinData: { pannable: true, viewType: true, filters: true } }, this.rootDoc);
+ const anchor = Docs.Create.CollectionAnchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), unrendered: true, presTransition: 500, annotationOn: this.rootDoc });
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, viewType: true, filters: true } }, this.rootDoc);
if (addAsAnnotation) {
if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) {
@@ -1636,6 +1523,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
componentDidMount() {
+ this.props.setContentView?.(this);
super.componentDidMount?.();
this.props.setBrushViewer?.(this.brushView);
setTimeout(
@@ -1643,7 +1531,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._firstRender = false;
this._disposers.groupBounds = reaction(
() => {
- if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
+ if (this.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() }));
return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
}
@@ -1652,7 +1540,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
cbounds => {
if (cbounds) {
const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
- const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)];
+ const p = [NumCast(this.layoutDoc[this.panXFieldKey]), NumCast(this.layoutDoc[this.panYFieldKey])];
const pbounds = {
x: cbounds.x - p[0] + c[0],
y: cbounds.y - p[1] + c[1],
@@ -1662,8 +1550,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (Number.isFinite(pbounds.r - pbounds.x) && Number.isFinite(pbounds.b - pbounds.y)) {
this.layoutDoc._width = pbounds.r - pbounds.x;
this.layoutDoc._height = pbounds.b - pbounds.y;
- this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2;
- this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2;
+ this.layoutDoc[this.panXFieldKey] = (cbounds.r + cbounds.x) / 2;
+ this.layoutDoc[this.panYFieldKey] = (cbounds.b + cbounds.y) / 2;
this.layoutDoc.x = pbounds.x;
this.layoutDoc.y = pbounds.y;
}
@@ -1696,7 +1584,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
} else {
const canvas = oldDiv;
const img = document.createElement('img'); // create a Image Element
- img.src = canvas.toDataURL(); //image source
+ try {
+ img.src = canvas.toDataURL(); //image source
+ } catch (e) {
+ console.log(e);
+ }
img.style.width = canvas.style.width;
img.style.height = canvas.style.height;
const newCan = newDiv as HTMLCanvasElement;
@@ -1750,7 +1642,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const nativeHeight = height;
return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight)
.then(async (data_url: any) => {
- const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename);
+ const returnedFilename = await Utils.convertDataUri(data_url, filename, noSuffix, replaceRootFilename);
cb(returnedFilename as string, nativeWidth, nativeHeight);
})
.catch(function (error: any) {
@@ -1781,8 +1673,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
if (deltaX !== 0 || deltaY !== 0) {
- this.Document._panY = NumCast(this.Document._panY) + deltaY / 2;
- this.Document._panX = NumCast(this.Document._panX) + deltaX / 2;
+ this.Document[this.panYFieldKey] = NumCast(this.Document[this.panYFieldKey]) + deltaY / 2;
+ this.Document[this.panXFieldKey] = NumCast(this.Document[this.panXFieldKey]) + deltaX / 2;
}
}
e.stopPropagation();
@@ -1806,8 +1698,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20;
const dim = Math.ceil(Math.sqrt(docs.length));
docs.forEach((doc, i) => {
- doc.x = NumCast(this.Document._panX) + (i % dim) * width - (width * dim) / 2;
- doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - (height * dim) / 2;
+ doc.x = NumCast(this.Document[this.panXFieldKey]) + (i % dim) * width - (width * dim) / 2;
+ doc.y = NumCast(this.Document[this.panYFieldKey]) + Math.floor(i / dim) * height - (height * dim) / 2;
});
};
@@ -1822,11 +1714,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
appearanceItems.push({
description: 'Reset View',
event: () => {
- this.props.Document._panX = this.props.Document._panY = 0;
+ this.props.Document[this.panXFieldKey] = this.props.Document[this.panYFieldKey] = 0;
this.props.Document[this.scaleFieldKey] = 1;
},
icon: 'compress-arrows-alt',
});
+ !Doc.noviceMode &&
+ appearanceItems.push({
+ description: 'Toggle auto arrange',
+ event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
+ icon: 'compress-arrows-alt',
+ });
+ if (this.props.setContentView === emptyFunction) {
+ !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
+ return;
+ }
!Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
appearanceItems.push({
description: `${this.fitContentsToBox ? 'Make Zoomable' : 'Scale to Window'}`,
@@ -1834,8 +1736,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt',
});
appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
- //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" });
- appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
+ !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
+ this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' });
@@ -1864,31 +1766,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
const mores = ContextMenu.Instance.findByDescription('More...');
const moreItems = mores && 'subitems' in mores ? mores.subitems : [];
- if (!Doc.noviceMode) {
- e.persist();
- moreItems.push({ description: 'Export collection', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
- moreItems.push({ description: 'Import exported collection', icon: 'upload', event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) });
- }
!mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' });
};
- importDocument = (x: number, y: number) => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = '.zip';
- input.onchange = _e => {
- input.files &&
- Doc.importDocument(input.files[0]).then(doc => {
- if (doc instanceof Doc) {
- const [xx, yy] = this.getTransform().transformPoint(x, y);
- (doc.x = xx), (doc.y = yy);
- this.props.addDocument?.(doc);
- }
- });
- };
- input.click();
- };
-
@undoBatch
@action
transcribeStrokes = (math: boolean) => {
@@ -1899,7 +1779,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const lines = text.split('\n');
const height = 30 + 15 * lines.length;
- this.props.ContainingCollectionView?.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
+ this.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
}
}
};
@@ -1946,11 +1826,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
});
- children = () => {
+ get children() {
this.incrementalRender();
- const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : [];
+ const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : this.props.children ? [this.props.children] : [];
return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />];
- };
+ }
@computed get placeholder() {
return (
@@ -1994,6 +1874,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
PanelHeight={this.props.PanelHeight}
panX={this.panX}
panY={this.panY}
+ nativeDimScaling={this.nativeDim}
zoomScaling={this.zoomScaling}
layoutDoc={this.layoutDoc}
isAnnotationOverlay={this.isAnnotationOverlay}
@@ -2028,6 +1909,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const wscale = nw ? this.props.PanelWidth() / nw : 1;
return wscale < hscale || this.layoutDoc.fitWidth ? wscale : hscale;
}
+ nativeDim = () => this.nativeDimScaling;
private groupDropDisposer?: DragManager.DragDropDisposer;
protected createGroupEventsTarget = (ele: HTMLDivElement) => {
@@ -2059,8 +1941,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
TraceMobx();
return (
<div
- className={'collectionfreeformview-container'}
- ref={this.createDashEventsTarget}
+ className="collectionfreeformview-container"
+ ref={r => {
+ this.createDashEventsTarget(r);
+ // prevent wheel events from passivly propagating up through containers
+ !this.props.isAnnotationOverlay && r?.addEventListener('wheel', (e: WheelEvent) => this.props.isSelected() && e.preventDefault(), { passive: false });
+ }}
onWheel={this.onPointerWheel}
onClick={this.onClick}
onPointerDown={this.onPointerDown}
@@ -2132,7 +2018,8 @@ interface CollectionFreeFormViewPannableContentsProps {
transform: () => string;
zoomScaling: () => number;
viewDefDivClick?: ScriptField;
- children: () => JSX.Element[];
+ children?: React.ReactNode | undefined;
+ //children: () => JSX.Element[];
transition?: string;
presPaths: () => JSX.Element | null;
presPinView?: boolean;
@@ -2226,7 +2113,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
//willChange: "transform"
}}>
- {this.props.children()}
+ {this.props.children}
{!this.props.brushView.width ? null : (
<div
className="collectionFreeFormView-brushView"
@@ -2254,6 +2141,7 @@ interface CollectionFreeFormViewBackgroundGridProps {
PanelWidth: () => number;
PanelHeight: () => number;
isAnnotationOverlay?: boolean;
+ nativeDimScaling: () => number;
zoomScaling: () => number;
layoutDoc: Doc;
cachedCenteringShiftX: number;
@@ -2271,10 +2159,10 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling();
const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling();
const renderGridSpace = gridSpace * this.props.zoomScaling();
- const w = this.props.PanelWidth() + 2 * renderGridSpace;
- const h = this.props.PanelHeight() + 2 * renderGridSpace;
+ const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
+ const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
const strokeStyle = Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'rgba(255,255,255,0.5)' : 'rgba(0, 0,0,0.5)';
- return (
+ return !this.props.nativeDimScaling() ? null : (
<canvas
className="collectionFreeFormView-grid"
width={w}
@@ -2309,21 +2197,24 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
}
export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) {
+ const browseTransitionTime = 500;
SelectionManager.DeselectAll();
- dv.props.focus(dv.props.Document, {
- willPanZoom: true,
- zoomScale: 0.8,
- afterFocus: async didMove => {
- if (!didMove) {
- const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
- const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview
- await ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5);
- }
- return ViewAdjustment.doNothing;
- },
- });
- Doc.linkFollowHighlight(dv?.props.Document, false);
+ if (
+ dv.props.focus(dv.props.Document, {
+ willZoomCentered: true,
+ zoomScale: 0.8,
+ zoomTime: browseTransitionTime,
+ }) === undefined
+ ) {
+ const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
+ let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview
+ ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime);
+ Doc.linkFollowHighlight(dv?.props.Document, false);
+ } else {
+ DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true });
+ }
}
ScriptingGlobals.add(CollectionBrowseClick);
ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
@@ -2337,9 +2228,27 @@ ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent';
runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.()));
});
-ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) {
- !readOnly &&
- SelectionManager.Views().forEach(view =>
- TabDocView.PinDoc(view.rootDoc, { currentFrame: Cast(view.rootDoc.currentFrame, 'number', null), pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) })
- );
+ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
+ SelectionManager.Views().forEach(view =>
+ view.props.pinToPres(view.rootDoc, {
+ currentFrame: Cast(view.rootDoc.currentFrame, 'number', null),
+ pinData: {
+ poslayoutview: pinContent,
+ dataview: pinContent,
+ },
+ pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()),
+ })
+ );
+});
+ScriptingGlobals.add(function bringToFront() {
+ SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc));
+});
+ScriptingGlobals.add(function sendToBack(doc: Doc) {
+ SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc, true));
+});
+ScriptingGlobals.add(function resetView() {
+ SelectionManager.Docs().forEach(doc => {
+ doc._panX = doc._panY = 0;
+ doc._viewScale = 1;
+ });
});
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 9581563ce..c9168d40a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -3,8 +3,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { observer } from 'mobx-react';
import { unimplementedFunction } from '../../../../Utils';
-import { DocumentType } from '../../../documents/DocumentTypes';
-import { SelectionManager } from '../../../util/SelectionManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
@observer
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index bd33c4d80..d443df0f3 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -19,7 +19,6 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { OpenWhere } from '../../nodes/DocumentView';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { VideoBox } from '../../nodes/VideoBox';
import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
import { PreviewCursor } from '../../PreviewCursor';
import { SubCollectionViewProps } from '../CollectionSubView';
@@ -167,7 +166,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
pasteImageBitmap((data: any, error: any) => {
error && console.log(error);
data &&
- VideoBox.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
+ Utils.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
this.props.Document['thumb-frozen'] = new ImageField(returnedfilename);
});
})
@@ -238,6 +237,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onPointerDown = (e: React.PointerEvent): void => {
+ // if (this.props.pointerEvents?.() === 'none') return;
this._downX = this._lastX = e.clientX;
this._downY = this._lastY = e.clientY;
if (!(e.nativeEvent as any).marqueeHit) {
@@ -346,6 +346,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onClick = (e: React.MouseEvent): void => {
+ if (this.props.pointerEvents?.() === 'none') return;
if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
if (Doc.ActiveTool === InkTool.None) {
if (!(e.nativeEvent as any).marqueeHit) {
@@ -395,9 +396,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
newCollection._height = this.Bounds.height;
newCollection._isGroup = makeGroup;
newCollection.forceActive = makeGroup;
+ newCollection.enableDragWhenActive = makeGroup;
newCollection.x = this.Bounds.left;
newCollection.y = this.Bounds.top;
- newCollection.fitwidth = true;
+ newCollection.fitWidth = true;
selected.forEach(d => (d.context = newCollection));
this.hideMarquee();
return newCollection;
@@ -526,7 +528,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
});
const summary = Docs.Create.TextDocument('', { backgroundColor: '#e2ad32', x: this.Bounds.left, y: this.Bounds.top, followLinkToggle: true, _width: 200, _height: 200, _fitContentsToBox: true, _showSidebar: true, title: 'overview' });
const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
- DocUtils.MakeLink({ doc: summary }, { doc: portal }, 'summary of:summarized by', '');
+ DocUtils.MakeLink(summary, portal, { linkRelationship: 'summary of:summarized by' });
portal.hidden = true;
this.props.addDocument?.(portal);
@@ -610,7 +612,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
return false;
}
- marqueeSelect(selectBackgrounds: boolean = true) {
+ marqueeSelect(selectBackgrounds: boolean = false) {
const selection: Doc[] = [];
const selectFunc = (doc: Doc) => {
const layoutDoc = Doc.Layout(doc);
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 9468c5f06..e8ae88ae5 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
import { Doc, Opt } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { DragManager } from '../../../util/DragManager';
import { SnappingManager } from '../../../util/SnappingManager';
@@ -186,7 +186,10 @@ export class CollectionGridView extends CollectionSubView() {
getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
return (
<DocumentView
- {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+ {...this.props}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ setContentView={emptyFunction}
Document={layout}
DataDoc={layout.resolvedDataDoc as Doc}
isContentActive={this.isChildContentActive}
@@ -196,7 +199,7 @@ export class CollectionGridView extends CollectionSubView() {
whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
onClick={this.onChildClickHandler}
renderDepth={this.props.renderDepth + 1}
- dontCenter={this.props.Document.centerY ? '' : 'y'}
+ dontCenter={this.props.Document.centerY ? undefined : 'y'}
/>
);
}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index 521bcda1e..3e3709827 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -1,9 +1,13 @@
@import '../../global/globalCssVariables';
@import '../../_nodeModuleOverrides';
+.collectionLinearView {
+ width: 100%;
+}
.collectionLinearView-label {
color: black;
background-color: $light-gray;
+ width: 100%;
}
.collectionLinearView-outer {
overflow: visible;
@@ -15,8 +19,6 @@
}
&.true {
- padding-left: 5px;
- padding-right: 5px;
border-left: $standard-border;
background-color: $medium-blue-alt;
}
@@ -29,7 +31,6 @@
display: flex;
height: 100%;
align-items: center;
- gap: 5px;
.collectionView {
overflow: visible !important;
@@ -101,13 +102,12 @@
background-color: $medium-blue;
padding: 5;
border-radius: 2px;
- height: 25;
+ height: 100%;
min-width: 25;
margin: 0;
color: $white;
display: flex;
font-weight: 100;
- width: fit-content;
transition: transform 0.2s;
align-items: center;
justify-content: center;
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index a2330c6b2..97eed7752 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { StylesProvider, Tooltip } from '@material-ui/core';
+import { Tooltip } from '@material-ui/core';
import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -11,7 +11,6 @@ import { CollectionViewType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
-import { Colors, Shadows } from '../../global/globalEnums';
import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
import { DocumentView } from '../../nodes/DocumentView';
import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
@@ -45,7 +44,7 @@ export class CollectionLinearView extends CollectionSubView() {
componentDidMount() {
this._widthDisposer = reaction(
- () => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * this.rootDoc[HeightSym]() : 10),
+ () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[WidthSym]() || this.dimension()) + tot + 4, 0) : 0),
width => this.childDocs.length && (this.layoutDoc._width = width),
{ fireImmediately: true }
);
@@ -79,7 +78,7 @@ export class CollectionLinearView extends CollectionSubView() {
}
};
- dimension = () => NumCast(this.rootDoc._height); // 2 * the padding
+ dimension = () => NumCast(this.rootDoc._height);
getTransform = (ele: Opt<HTMLDivElement>) => {
if (!ele) return Transform.Identity();
const { scale, translateX, translateY } = Utils.GetScreenTransform(ele);
@@ -150,9 +149,13 @@ export class CollectionLinearView extends CollectionSubView() {
<span className="bottomPopup-text">
Currently playing:
{CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => (
- <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, { willPanZoom: true }, undefined, [])}>
- {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')}
- </span>
+ <>
+ <span className="audio-title" onPointerDown={() => DocumentManager.Instance.showDocument(clip.rootDoc, { willZoomCentered: true })}>
+ {clip.rootDoc.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? ' ' : ',')}
+ </span>
+ <FontAwesomeIcon icon={!clip.ComponentView?.IsPlaying?.() ? 'play' : 'pause'} size="lg" onPointerDown={() => clip.ComponentView?.TogglePause?.()} />{' '}
+ <FontAwesomeIcon icon="times" size="lg" onPointerDown={() => clip.ComponentView?.Pause?.()} />{' '}
+ </>
))}
</span>
</span>
@@ -175,10 +178,10 @@ export class CollectionLinearView extends CollectionSubView() {
ref={r => (dref = r || undefined)}
style={{
pointerEvents: 'all',
- width: nested ? undefined : NumCast(doc._width),
- height: nested ? undefined : NumCast(doc._height),
- marginLeft: !nested ? 2.5 : 0,
- marginRight: !nested ? 2.5 : 0,
+ width: NumCast(doc._width),
+ height: NumCast(doc._height),
+ marginLeft: 2,
+ marginRight: 2,
// width: NumCast(pair.layout._width),
// height: NumCast(pair.layout._height),
}}>
@@ -194,7 +197,7 @@ export class CollectionLinearView extends CollectionSubView() {
rootSelected={this.props.isSelected}
removeDocument={this.props.removeDocument}
ScreenToLocalTransform={docXf}
- PanelWidth={nested ? doc[WidthSym] : this.dimension}
+ PanelWidth={doc[WidthSym]}
PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension}
renderDepth={this.props.renderDepth + 1}
dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)}
@@ -206,8 +209,6 @@ export class CollectionLinearView extends CollectionSubView() {
docFilters={this.props.docFilters}
docRangeFilters={this.props.docRangeFilters}
searchFilterDocs={this.props.searchFilterDocs}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
hideResizeHandles={true}
/>
</div>
@@ -231,7 +232,7 @@ export class CollectionLinearView extends CollectionSubView() {
return (
<div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: this.layoutDoc.linearViewIsExpanded ? undefined : 'transparent' }}>
- <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu}>
+ <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension() }}>
{!this.props.Document.linearViewExpandable ? null : (
<Tooltip title={<div className="dash-tooltip">{isExpanded ? 'Close' : 'Open'}</div>} placement="top">
{menuOpener}
@@ -248,8 +249,7 @@ export class CollectionLinearView extends CollectionSubView() {
self: this.rootDoc,
_readOnly_: false,
scriptContext: this.props.scriptContext,
- thisContainer: this.props.ContainingCollectionDoc,
- documentView: this.props.docViewPath().lastElement(),
+ documentView: this.props.DocumentView?.(),
});
this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked;
})}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index b88da5191..78d3d1b6e 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -2,14 +2,13 @@ import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../../fields/Doc';
-import { List } from '../../../../fields/List';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { returnFalse } from '../../../../Utils';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
-import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionMulticolumnView.scss';
import ResizeBar from './MulticolumnResizer';
@@ -234,7 +233,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
- focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options);
isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false);
getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
@@ -260,12 +258,10 @@ export class CollectionMulticolumnView extends CollectionSubView() {
hideResizeHandles={this.props.childHideResizeHandles?.()}
hideDecorationTitle={this.props.childHideDecorationTitle?.()}
fitContentsToBox={this.props.fitContentsToBox}
- focus={this.focusDocument}
+ focus={this.props.focus}
docFilters={this.childDocFilters}
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
dontRegisterView={this.props.dontRegisterView}
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 407deaabd..4d61dc272 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -2,14 +2,13 @@ import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../../fields/Doc';
-import { List } from '../../../../fields/List';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { returnFalse } from '../../../../Utils';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
-import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionMultirowView.scss';
import HeightLabel from './MultirowHeightLabel';
@@ -234,7 +233,6 @@ export class CollectionMultirowView extends CollectionSubView() {
onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
- focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options);
isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false);
getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
@@ -263,8 +261,6 @@ export class CollectionMultirowView extends CollectionSubView() {
docFilters={this.childDocFilters}
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
dontRegisterView={this.props.dontRegisterView}
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
deleted file mode 100644
index 97a6c5c18..000000000
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ /dev/null
@@ -1,683 +0,0 @@
-import React = require('react');
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import { extname } from 'path';
-import DatePicker from 'react-datepicker';
-import { CellInfo } from 'react-table';
-import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { Id } from '../../../../fields/FieldSymbols';
-import { List } from '../../../../fields/List';
-import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { ComputedField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DateCast, FieldValue, StrCast } from '../../../../fields/Types';
-import { ImageField } from '../../../../fields/URLField';
-import { emptyFunction, Utils } from '../../../../Utils';
-import { Docs } from '../../../documents/Documents';
-import { DocumentType } from '../../../documents/DocumentTypes';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from '../../../util/DragManager';
-import { KeyCodes } from '../../../util/KeyCodes';
-import { CompileScript } from '../../../util/Scripting';
-import { SearchUtil } from '../../../util/SearchUtil';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { undoBatch } from '../../../util/UndoManager';
-import '../../../views/DocumentDecorations.scss';
-import { EditableView } from '../../EditableView';
-import { MAX_ROW_HEIGHT } from '../../global/globalCssVariables.scss';
-import { DocumentIconContainer } from '../../nodes/DocumentIcon';
-import { OverlayView } from '../../OverlayView';
-import { CollectionView } from '../CollectionView';
-import './CollectionSchemaView.scss';
-import { OpenWhere } from '../../nodes/DocumentView';
-import { PinProps } from '../../nodes/trails';
-
-// intialize cell properties
-export interface CellProps {
- row: number;
- col: number;
- rowProps: CellInfo;
- // currently unused
- CollectionView: Opt<CollectionView>;
- // currently unused
- ContainingCollection: Opt<CollectionView>;
- Document: Doc;
- // column name
- fieldKey: string;
- // currently unused
- renderDepth: number;
- // called when a button is pressed on the node itself
- addDocTab: (document: Doc, where: OpenWhere) => boolean;
- pinToPres: (document: Doc, pinProps: PinProps) => void;
- moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
- isFocused: boolean;
- changeFocusedCellByIndex: (row: number, col: number) => void;
- // set whether the cell is in the isEditing mode
- setIsEditing: (isEditing: boolean) => void;
- isEditable: boolean;
- setPreviewDoc: (doc: Doc) => void;
- setComputed: (script: string, doc: Doc, field: string, row: number, col: number) => boolean;
- getField: (row: number, col?: number) => void;
- // currnetly unused
- showDoc: (doc: Doc | undefined, dataDoc?: any, screenX?: number, screenY?: number) => void;
-}
-
-@observer
-export class CollectionSchemaCell extends React.Component<CellProps> {
- // return a field key that is corrected for whether it COMMENT
- public static resolvedFieldKey(column: string, rowDoc: Doc) {
- const fieldKey = column;
- if (fieldKey.startsWith('*')) {
- const rootKey = fieldKey.substring(1);
- const allKeys = [...Array.from(Object.keys(rowDoc)), ...Array.from(Object.keys(Doc.GetProto(rowDoc)))];
- const matchedKeys = allKeys.filter(key => key.includes(rootKey));
- if (matchedKeys.length) return matchedKeys[0];
- }
- return fieldKey;
- }
- @observable protected _isEditing: boolean = false;
- protected _focusRef = React.createRef<HTMLDivElement>();
- protected _rowDoc = this.props.rowProps.original;
- // Gets the serialized data in proto form of the base proto that this document's proto inherits from
- protected _rowDataDoc = Doc.GetProto(this.props.rowProps.original);
- // methods for dragging and dropping
- protected _dropDisposer?: DragManager.DragDropDisposer;
- @observable contents: string = '';
-
- componentDidMount() {
- document.addEventListener('keydown', this.onKeyDown);
- }
- componentWillUnmount() {
- document.removeEventListener('keydown', this.onKeyDown);
- }
-
- @action
- onKeyDown = (e: KeyboardEvent): void => {
- // If a cell is editable and clicked, hitting enter shoudl allow the user to edit it
- if (this.props.isFocused && this.props.isEditable && e.keyCode === KeyCodes.ENTER) {
- document.removeEventListener('keydown', this.onKeyDown);
- this._isEditing = true;
- this.props.setIsEditing(true);
- }
- };
-
- @action
- isEditingCallback = (isEditing: boolean): void => {
- // a general method that takes a boolean that determines whether the cell should be in
- // is-editing mode
- // remove the event listener if it's there
- document.removeEventListener('keydown', this.onKeyDown);
- // it's not already in is-editing mode, re-add the event listener
- isEditing && document.addEventListener('keydown', this.onKeyDown);
- this._isEditing = isEditing;
- this.props.setIsEditing(isEditing);
- this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
- };
-
- @action
- onPointerDown = async (e: React.PointerEvent): Promise<void> => {
- // pan to the cell
- this.onItemDown(e);
- // focus on it
- this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
- this.props.setPreviewDoc(this.props.rowProps.original);
-
- let url: string;
- if ((url = StrCast(this.props.rowProps.row.href))) {
- // opens up the the doc in a new window, blurring the old one
- try {
- new URL(url);
- const temp = window.open(url)!;
- temp.blur();
- window.focus();
- } catch {}
- }
-
- const doc = Cast(this._rowDoc[this.renderFieldKey], Doc, null);
- doc && this.props.setPreviewDoc(doc);
- };
-
- @undoBatch
- applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => {
- // apply a specified change to the cell
- const res = run({ this: doc, $r: row, $c: col, $: (r: number = 0, c: number = 0) => this.props.getField(r + row, c + col) });
- if (!res.success) return false;
- // change what is rendered to this new changed cell content
- doc[this.renderFieldKey] = res.result;
- return true;
- // return whether the change was successful
- };
-
- private drop = (e: Event, de: DragManager.DropEvent) => {
- // if the drag has data at its completion
- if (de.complete.docDragData) {
- // if only one doc was dragged
- if (de.complete.docDragData.draggedDocuments.length === 1) {
- // update the renderFieldKey
- this._rowDataDoc[this.renderFieldKey] = de.complete.docDragData.draggedDocuments[0];
- } else {
- // create schema document reflecting the new column arrangement
- const coll = Docs.Create.SchemaDocument([new SchemaHeaderField('title', '#f1efeb')], de.complete.docDragData.draggedDocuments, {});
- this._rowDataDoc[this.renderFieldKey] = coll;
- }
- e.stopPropagation();
- }
- };
-
- protected dropRef = (ele: HTMLElement | null) => {
- // if the drop disposer is not undefined, run its function
- this._dropDisposer?.();
- // if ele is not null, give ele a non-undefined drop disposer
- ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
- };
-
- returnHighlights(contents: string, positions?: number[]) {
- if (positions) {
- const results = [];
- StrCast(this.props.Document._searchString);
- const length = StrCast(this.props.Document._searchString).length;
- const color = contents ? 'black' : 'grey';
-
- results.push(
- <span key="-1" style={{ color }}>
- {contents?.slice(0, positions[0])}
- </span>
- );
- positions.forEach((num, cur) => {
- results.push(
- <span key={'start' + cur} style={{ backgroundColor: '#FFFF00', color }}>
- {contents?.slice(num, num + length)}
- </span>
- );
- let end = 0;
- cur === positions.length - 1 ? (end = contents.length) : (end = positions[cur + 1]);
- results.push(
- <span key={'end' + cur} style={{ color }}>
- {contents?.slice(num + length, end)}
- </span>
- );
- });
- return results;
- }
- return <span style={{ color: contents ? 'black' : 'grey' }}>{contents ? contents?.valueOf() : 'undefined'}</span>;
- }
-
- @computed get renderFieldKey() {
- // gets the resolved field key of this cell
- return CollectionSchemaCell.resolvedFieldKey(this.props.rowProps.column.id!, this.props.rowProps.original);
- }
-
- onItemDown = async (e: React.PointerEvent) => {
- // if the document is a document used to change UI for search results in schema view
- if (this.props.Document._searchDoc) {
- const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc);
- const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null);
- // Jump to the this document
- DocumentManager.Instance.jumpToDocument(this._rowDoc, { willPan: true }, emptyFunction, targetContext ? [targetContext] : [], () => this.props.setPreviewDoc(this._rowDoc));
- }
- };
-
- renderCellWithType(type: string | undefined) {
- const dragRef: React.RefObject<HTMLDivElement> = React.createRef();
-
- // the column
- const fieldKey = this.renderFieldKey;
- // the exact cell
- const field = this._rowDoc[fieldKey];
-
- const onPointerEnter = (e: React.PointerEvent): void => {
- // e.buttons === 1 means the left moue pointer is down
- if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === 'document' || type === undefined)) {
- dragRef.current!.className = 'collectionSchemaView-cellContainer doc-drag-over';
- }
- };
- const onPointerLeave = (e: React.PointerEvent): void => {
- // change the class name to indicate that the cell is no longer being dragged
- dragRef.current!.className = 'collectionSchemaView-cellContainer';
- };
-
- let contents = Field.toString(field as Field);
- // display 2 hyphens instead of a blank box for empty cells
- contents = contents === '' ? '--' : contents;
-
- // classname reflects the tatus of the cell
- let className = 'collectionSchemaView-cellWrapper';
- if (this._isEditing) className += ' editing';
- if (this.props.isFocused && this.props.isEditable) className += ' focused';
- if (this.props.isFocused && !this.props.isEditable) className += ' inactive';
-
- const positions = [];
- if (StrCast(this.props.Document._searchString).toLowerCase() !== '') {
- // term is ...promise pending... if the field is a Promise, otherwise it is the cell's contents
- let term = field instanceof Promise ? '...promise pending...' : contents.toLowerCase();
- const search = StrCast(this.props.Document._searchString).toLowerCase();
- let start = term.indexOf(search);
- let tally = 0;
- // if search is found in term
- if (start !== -1) {
- positions.push(start);
- }
- // if search is found in term, continue finding all instances of search in term
- while (start < contents?.length && start !== -1) {
- term = term.slice(start + search.length + 1);
- tally += start + search.length + 1;
- start = term.indexOf(search);
- positions.push(tally + start);
- }
- // remove the last position
- if (positions.length > 1) {
- positions.pop();
- }
- }
- const placeholder = type === 'number' ? '0' : contents === '' ? '--' : 'undefined';
- return (
- <div
- className="collectionSchemaView-cellContainer"
- style={{ cursor: field instanceof Doc ? 'grab' : 'auto' }}
- ref={dragRef}
- onPointerDown={this.onPointerDown}
- onClick={action(e => (this._isEditing = true))}
- onPointerEnter={onPointerEnter}
- onPointerLeave={onPointerLeave}>
- <div className={className} ref={this._focusRef} tabIndex={-1}>
- <div className="collectionSchemaView-cellContents" ref={type === undefined || type === 'document' ? this.dropRef : null}>
- {!this.props.Document._searchDoc ? (
- <EditableView
- editing={this._isEditing}
- isEditingCallback={this.isEditingCallback}
- display={'inline'}
- contents={contents}
- height={'auto'}
- maxHeight={Number(MAX_ROW_HEIGHT)}
- placeholder={placeholder}
- GetValue={() => {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(field));
- const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined;
- const cfinalScript = cscript?.split('return')[cscript.split('return').length - 1];
- return cscript ? (cfinalScript?.endsWith(';') ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : Field.IsField(cfield) ? Field.toScriptString(cfield) : '';
- }}
- SetValue={action((value: string) => {
- // sets what is displayed after the user makes an input
- let retVal = false;
- if (value.startsWith(':=') || value.startsWith('=:=')) {
- // decides how to compute a value when given either of the above strings
- const script = value.substring(value.startsWith('=:=') ? 3 : 2);
- retVal = this.props.setComputed(script, value.startsWith(':=') ? this._rowDataDoc : this._rowDoc, this.renderFieldKey, this.props.row, this.props.col);
- } else {
- // check if the input is a number
- let inputIsNum = true;
- for (const s of value) {
- if (isNaN(parseInt(s)) && !(s === '.') && !(s === ',')) {
- inputIsNum = false;
- }
- }
- // check if the input is a boolean
- const inputIsBool: boolean = value === 'false' || value === 'true';
- // what to do in the case
- if (!inputIsNum && !inputIsBool && !value.startsWith('=')) {
- // if it's not a number, it's a string, and should be processed as such
- // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically
- // after each edit
- let valueSansQuotes = value;
- if (this._isEditing) {
- const vsqLength = valueSansQuotes.length;
- // get rid of outer quotes
- valueSansQuotes = valueSansQuotes.substring(value.startsWith('"') ? 1 : 0, valueSansQuotes.charAt(vsqLength - 1) === '"' ? vsqLength - 1 : vsqLength);
- }
- let inputAsString = '"';
- // escape any quotes in the string
- for (const i of valueSansQuotes) {
- if (i === '"') {
- inputAsString += '\\"';
- } else {
- inputAsString += i;
- }
- }
- // add a closing quote
- inputAsString += '"';
- //two options here: we can strip off outer quotes or we can figure out what's going on with the script
- const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
- const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length;
- // change it if a change is made, otherwise, just compile using the old cell conetnts
- script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
- // handle numbers and expressions
- } else if (inputIsNum || value.startsWith('=')) {
- //TODO: make accept numbers
- const inputscript = value.substring(value.startsWith('=') ? 1 : 0);
- // if commas are not stripped, the parser only considers the numbers after the last comma
- let inputSansCommas = '';
- for (const s of inputscript) {
- if (!(s === ',')) {
- inputSansCommas += s;
- }
- }
- const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
- const changeMade = value.length - 2 !== value.length;
- script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
- // handle booleans
- } else if (inputIsBool) {
- const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
- const changeMade = value.length - 2 !== value.length;
- script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
- }
- }
- if (retVal) {
- this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing'
- this.props.setIsEditing(false);
- }
- return retVal;
- })}
- OnFillDown={async (value: string) => {
- // computes all of the value preceded by :=
- const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
- script.compiled &&
- DocListCast(this.props.Document[this.props.fieldKey]).forEach((doc, i) =>
- value.startsWith(':=') ? this.props.setComputed(value.substring(2), Doc.GetProto(doc), this.renderFieldKey, i, this.props.col) : this.applyToDoc(Doc.GetProto(doc), i, this.props.col, script.run)
- );
- }}
- />
- ) : (
- this.returnHighlights(contents, positions)
- )}
- </div>
- </div>
- </div>
- );
- }
-
- render() {
- return this.renderCellWithType(undefined);
- }
-}
-
-@observer
-export class CollectionSchemaNumberCell extends CollectionSchemaCell {
- render() {
- return this.renderCellWithType('number');
- }
-}
-
-@observer
-export class CollectionSchemaBooleanCell extends CollectionSchemaCell {
- render() {
- return this.renderCellWithType('boolean');
- }
-}
-
-@observer
-export class CollectionSchemaStringCell extends CollectionSchemaCell {
- render() {
- return this.renderCellWithType('string');
- }
-}
-
-@observer
-export class CollectionSchemaDateCell extends CollectionSchemaCell {
- @computed get _date(): Opt<DateField> {
- // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
- return this._rowDoc[this.renderFieldKey] instanceof DateField ? DateCast(this._rowDoc[this.renderFieldKey]) : undefined;
- }
-
- @action
- handleChange = (date: any) => {
- // const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
- // if (script.compiled) {
- // this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
- // } else {
- // ^ DateCast is always undefined for some reason, but that is what the field should be set to
- this._rowDoc[this.renderFieldKey] = new DateField(date as Date);
- //}
- };
-
- render() {
- return !this.props.isFocused ? (
- <span onPointerDown={this.onPointerDown}>{this._date ? Field.toString(this._date as Field) : '--'}</span>
- ) : (
- <DatePicker selected={this._date?.date || new Date()} onSelect={date => this.handleChange(date)} onChange={date => this.handleChange(date)} />
- );
- }
-}
-
-@observer
-export class CollectionSchemaDocCell extends CollectionSchemaCell {
- _overlayDisposer?: () => void;
-
- @computed get _doc() {
- return FieldValue(Cast(this._rowDoc[this.renderFieldKey], Doc));
- }
-
- @action
- onSetValue = (value: string) => {
- this._doc && (Doc.GetProto(this._doc).title = value);
-
- const script = CompileScript(value, {
- addReturn: true,
- typecheck: true,
- transformer: DocumentIconContainer.getTransformer(),
- });
- // compile the script
- const results = script.compiled && script.run();
- // if the script was compiled and run
- if (results && results.success) {
- this._rowDoc[this.renderFieldKey] = results.result;
- return true;
- }
- return false;
- };
-
- componentWillUnmount() {
- this.onBlur();
- }
-
- onBlur = () => {
- this._overlayDisposer?.();
- };
- onFocus = () => {
- this.onBlur();
- this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
- };
-
- @action
- isEditingCallback = (isEditing: boolean): void => {
- // the isEditingCallback from a general CollectionSchemaCell
- document.removeEventListener('keydown', this.onKeyDown);
- isEditing && document.addEventListener('keydown', this.onKeyDown);
- this._isEditing = isEditing;
- this.props.setIsEditing(isEditing);
- this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
- };
-
- render() {
- // if there's a doc, render it
- return !this._doc ? (
- this.renderCellWithType('document')
- ) : (
- <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
- <div className="collectionSchemaView-cellContents-document" style={{ padding: '5.9px' }} ref={this.dropRef} onFocus={this.onFocus} onBlur={this.onBlur}>
- <EditableView
- editing={this._isEditing}
- isEditingCallback={this.isEditingCallback}
- display={'inline'}
- contents={this._doc.title || '--'}
- height={'auto'}
- maxHeight={Number(MAX_ROW_HEIGHT)}
- GetValue={() => StrCast(this._doc?.title)}
- SetValue={action((value: string) => {
- this.onSetValue(value);
- return true;
- })}
- />
- </div>
- <div onClick={() => this._doc && this.props.addDocTab(this._doc, OpenWhere.addRight)} className="collectionSchemaView-cellContents-docButton">
- <FontAwesomeIcon icon="external-link-alt" size="lg" />
- </div>
- </div>
- );
- }
-}
-
-@observer
-export class CollectionSchemaImageCell extends CollectionSchemaCell {
- choosePath(url: URL) {
- if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href
- if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
- if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question
-
- const ext = extname(url.href);
- return url.href.replace(ext, '_o' + ext);
- }
-
- render() {
- const field = Cast(this._rowDoc[this.renderFieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
- const alts = DocListCast(this._rowDoc[this.renderFieldKey + '-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)
- .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
- const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
- // If there is a path, follow it; otherwise, follow a link to a default image icon
- const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
-
- const aspect = Doc.NativeAspect(this._rowDoc); // aspect ratio
- let width = Math.min(75, this.props.rowProps.width); // get a with that is no smaller than 75px
- const height = Math.min(75, width / aspect); // get a height either proportional to that or 75 px
- width = height * aspect; // increase the width of the image if necessary to maintain proportionality
-
- const reference = React.createRef<HTMLDivElement>();
- return (
- <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
- <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} ref={reference}>
- <img src={url[0]} width={paths.length ? width : '20px'} height={paths.length ? height : '20px'} />
- </div>
- </div>
- );
- }
-}
-
-@observer
-export class CollectionSchemaListCell extends CollectionSchemaCell {
- _overlayDisposer?: () => void;
-
- @computed get _field() {
- return this._rowDoc[this.renderFieldKey];
- }
- @computed get _optionsList() {
- return this._field as List<any>;
- }
- @observable private _opened = false; // whether the list is opened
- @observable private _text = 'select an item';
- @observable private _selectedNum = 0; // the index of the list item selected
-
- @action
- onSetValue = (value: string) => {
- // change if it's a document
- this._optionsList[this._selectedNum] = this._text = value;
-
- (this._field as List<any>).splice(this._selectedNum, 1, value);
- };
-
- @action
- onSelected = (element: string, index: number) => {
- // if an item is selected, the private variables should update to reflect this
- this._text = element;
- this._selectedNum = index;
- };
-
- onFocus = () => {
- this._overlayDisposer?.();
- this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
- };
-
- render() {
- const link = false;
- const reference = React.createRef<HTMLDivElement>();
-
- // if the list is not opened, don't display it; otherwise, do.
- if (this._optionsList?.length) {
- const options = !this._opened ? null : (
- <div>
- {this._optionsList.map((element, index) => {
- const val = Field.toString(element);
- return (
- <div className="collectionSchemaView-dropdownOption" key={index} style={{ padding: '6px' }} onPointerDown={e => this.onSelected(StrCast(element), index)}>
- {val}
- </div>
- );
- })}
- </div>
- );
-
- const plainText = <div style={{ padding: '5.9px' }}>{this._text}</div>;
- const textarea = (
- <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} style={{ padding: '5.9px' }} ref={this.dropRef}>
- <EditableView
- editing={this._isEditing}
- isEditingCallback={this.isEditingCallback}
- display={'inline'}
- contents={this._text}
- height={'auto'}
- maxHeight={Number(MAX_ROW_HEIGHT)}
- GetValue={() => this._text}
- SetValue={action((value: string) => {
- // add special for params
- this.onSetValue(value);
- return true;
- })}
- />
- </div>
- );
-
- //☰
- return (
- <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
- <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} ref={reference}>
- <div className="collectionSchemaView-dropDownWrapper">
- <button type="button" className="collectionSchemaView-dropdownButton" style={{ right: 'length', position: 'relative' }} onClick={action(e => (this._opened = !this._opened))}>
- <FontAwesomeIcon icon={this._opened ? 'caret-up' : 'caret-down'} size="sm" />
- </button>
- <div className="collectionSchemaView-dropdownText"> {link ? plainText : textarea} </div>
- </div>
- {options}
- </div>
- </div>
- );
- }
- return this.renderCellWithType('list');
- }
-}
-
-@observer
-export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
- @computed get _isChecked() {
- return BoolCast(this._rowDoc[this.renderFieldKey]);
- }
-
- render() {
- const reference = React.createRef<HTMLDivElement>();
- return (
- <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
- <input type="checkbox" checked={this._isChecked} onChange={e => (this._rowDoc[this.renderFieldKey] = e.target.checked)} />
- </div>
- );
- }
-}
-
-@observer
-export class CollectionSchemaButtons extends CollectionSchemaCell {
- // the navigation buttons for schema view when it is used for search.
- render() {
- return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? (
- <></>
- ) : (
- <div style={{ paddingTop: 8, paddingLeft: 3 }}>
- <button style={{ padding: 2, left: 77 }} onClick={() => Doc.SearchMatchNext(this._rowDoc, true)}>
- <FontAwesomeIcon icon="arrow-up" size="sm" />
- </button>
- <button style={{ padding: 2 }} onClick={() => Doc.SearchMatchNext(this._rowDoc, false)}>
- <FontAwesomeIcon icon="arrow-down" size="sm" />
- </button>
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
deleted file mode 100644
index 9653f2808..000000000
--- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
+++ /dev/null
@@ -1,513 +0,0 @@
-import React = require("react");
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction, trace } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt, StrListCast } from "../../../../fields/Doc";
-import { listSpec } from "../../../../fields/Schema";
-import { PastelSchemaPalette, SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { ScriptField } from "../../../../fields/ScriptField";
-import { Cast, StrCast } from "../../../../fields/Types";
-import { undoBatch } from "../../../util/UndoManager";
-import { CollectionView } from "../CollectionView";
-import { ColumnType } from "./CollectionSchemaView";
-import "./CollectionSchemaView.scss";
-
-const higflyout = require("@hig/flyout");
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-
-
-export interface AddColumnHeaderProps {
- createColumn: () => void;
-}
-
-@observer
-export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHeaderProps> {
- // the button that allows the user to add a column
- render() {
- return <button className="add-column" onClick={() => this.props.createColumn()}>
- <FontAwesomeIcon icon="plus" size="sm" />
- </button>;
- }
-}
-
-export interface ColumnMenuProps {
- columnField: SchemaHeaderField;
- // keyValue: string;
- possibleKeys: string[];
- existingKeys: string[];
- // keyType: ColumnType;
- typeConst: boolean;
- menuButtonContent: JSX.Element;
- addNew: boolean;
- onSelect: (oldKey: string, newKey: string, addnew: boolean) => void;
- setIsEditing: (isEditing: boolean) => void;
- deleteColumn: (column: string) => void;
- onlyShowOptions: boolean;
- setColumnType: (column: SchemaHeaderField, type: ColumnType) => void;
- setColumnSort: (column: SchemaHeaderField, desc: boolean | undefined) => void;
- anchorPoint?: any;
- setColumnColor: (column: SchemaHeaderField, color: string) => void;
-}
-@observer
-export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> {
- @observable private _isOpen: boolean = false;
- @observable private _node: HTMLDivElement | null = null;
-
- componentDidMount() { document.addEventListener("pointerdown", this.detectClick); }
-
- componentWillUnmount() { document.removeEventListener("pointerdown", this.detectClick); }
-
- @action
- detectClick = (e: PointerEvent) => {
- !this._node?.contains(e.target as Node) && this.props.setIsEditing(this._isOpen = false);
- }
-
- @action
- toggleIsOpen = (): void => {
- this.props.setIsEditing(this._isOpen = !this._isOpen);
- }
-
- changeColumnType = (type: ColumnType) => {
- this.props.setColumnType(this.props.columnField, type);
- }
-
- changeColumnSort = (desc: boolean | undefined) => {
- this.props.setColumnSort(this.props.columnField, desc);
- }
-
- changeColumnColor = (color: string) => {
- this.props.setColumnColor(this.props.columnField, color);
- }
-
- @action
- setNode = (node: HTMLDivElement): void => {
- if (node) {
- this._node = node;
- }
- }
-
- renderTypes = () => {
- if (this.props.typeConst) return (null);
-
- const type = this.props.columnField.type;
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Column type:</label>
- <div className="columnMenu-types">
- <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Any)}>
- <FontAwesomeIcon icon={"align-justify"} size="sm" />
- Any
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Number)}>
- <FontAwesomeIcon icon={"hashtag"} size="sm" />
- Number
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.String)}>
- <FontAwesomeIcon icon={"font"} size="sm" />
- Text
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Boolean)}>
- <FontAwesomeIcon icon={"check-square"} size="sm" />
- Checkbox
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.List)}>
- <FontAwesomeIcon icon={"list-ul"} size="sm" />
- List
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Doc)}>
- <FontAwesomeIcon icon={"file"} size="sm" />
- Document
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Image)}>
- <FontAwesomeIcon icon={"image"} size="sm" />
- Image
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Date)}>
- <FontAwesomeIcon icon={"calendar"} size="sm" />
- Date
- </div>
- </div>
- </div >
- );
- }
-
- renderSorting = () => {
- const sort = this.props.columnField.desc;
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Sort by:</label>
- <div className="columnMenu-sort">
- <div className={"columnMenu-option" + (sort === true ? " active" : "")} onClick={() => this.changeColumnSort(true)}>
- <FontAwesomeIcon icon="sort-amount-down" size="sm" />
- Sort descending
- </div>
- <div className={"columnMenu-option" + (sort === false ? " active" : "")} onClick={() => this.changeColumnSort(false)}>
- <FontAwesomeIcon icon="sort-amount-up" size="sm" />
- Sort ascending
- </div>
- <div className="columnMenu-option" onClick={() => this.changeColumnSort(undefined)}>
- <FontAwesomeIcon icon="times" size="sm" />
- Clear sorting
- </div>
- </div>
- </div>
- );
- }
-
- renderColors = () => {
- const selected = this.props.columnField.color;
-
- const pink = PastelSchemaPalette.get("pink2");
- const purple = PastelSchemaPalette.get("purple2");
- const blue = PastelSchemaPalette.get("bluegreen1");
- const yellow = PastelSchemaPalette.get("yellow4");
- const red = PastelSchemaPalette.get("red2");
- const gray = "#f1efeb";
-
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Color:</label>
- <div className="columnMenu-colors">
- <div className={"columnMenu-colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)}></div>
- </div>
- </div>
- );
- }
-
- renderContent = () => {
- return (
- <div className="collectionSchema-header-menuOptions">
- {this.props.onlyShowOptions ? <></> :
- <>
- {this.renderTypes()}
- {this.renderSorting()}
- {this.renderColors()}
- <div className="collectionSchema-headerMenu-group">
- <button onClick={() => this.props.deleteColumn(this.props.columnField.heading)}>Hide Column</button>
- </div>
- </>
- }
- </div>
- );
- }
-
- render() {
- return (
- <div className="collectionSchema-header-menu" ref={this.setNode}>
- <Flyout anchorPoint={this.props.anchorPoint ? this.props.anchorPoint : anchorPoints.TOP_CENTER} content={this.renderContent()}>
- <div className="collectionSchema-header-toggler" onClick={() => this.toggleIsOpen()}>{this.props.menuButtonContent}</div>
- </ Flyout >
- </div>
- );
- }
-}
-
-
-export interface KeysDropdownProps {
- keyValue: string;
- possibleKeys: string[];
- existingKeys: string[];
- canAddNew: boolean;
- addNew: boolean;
- onSelect: (oldKey: string, newKey: string, addnew: boolean, filter?: string) => void;
- setIsEditing: (isEditing: boolean) => void;
- width?: string;
- docs?: Doc[];
- Document: Doc;
- dataDoc: Doc | undefined;
- fieldKey: string;
- ContainingCollectionDoc: Doc | undefined;
- ContainingCollectionView: Opt<CollectionView>;
- active?: (outsideReaction?: boolean) => boolean | undefined;
- openHeader: (column: any, screenx: number, screeny: number) => void;
- col: SchemaHeaderField;
- icon: IconProp;
-}
-@observer
-export class KeysDropdown extends React.Component<KeysDropdownProps> {
- @observable private _key: string = this.props.keyValue;
- @observable private _searchTerm: string = this.props.keyValue + ":";
- @observable private _isOpen: boolean = false;
- @observable private _node: HTMLDivElement | null = null;
- @observable private _inputRef: React.RefObject<HTMLInputElement> = React.createRef();
-
- @action setSearchTerm = (value: string): void => { this._searchTerm = value; };
- @action setKey = (key: string): void => { this._key = key; };
- @action setIsOpen = (isOpen: boolean): void => { this._isOpen = isOpen; };
-
- @action
- onSelect = (key: string): void => {
- this.props.onSelect(this._key, key, this.props.addNew);
- this.setKey(key);
- this._isOpen = false;
- this.props.setIsEditing(false);
- }
-
- @action
- setNode = (node: HTMLDivElement): void => {
- if (node) {
- this._node = node;
- }
- }
-
- componentDidMount() {
- document.addEventListener("pointerdown", this.detectClick);
- const filters = Cast(this.props.Document._docFilters, listSpec("string"));
- if (filters?.some(filter => filter.split(":")[0] === this._key)) {
- runInAction(() => this.closeResultsVisibility = "contents");
- }
- }
-
- @action
- detectClick = (e: PointerEvent): void => {
- if (this._node && this._node.contains(e.target as Node)) {
- } else {
- this._isOpen = false;
- this.props.setIsEditing(false);
- }
- }
-
- private tempfilter: string = "";
- @undoBatch
- onKeyDown = (e: React.KeyboardEvent): void => {
- if (e.key === "Enter") {
- e.stopPropagation();
- if (this._searchTerm.includes(":")) {
- const colpos = this._searchTerm.indexOf(":");
- const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length);
- if (temp === "") {
- Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove");
- this.updateFilter();
- }
- else {
- Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove");
- this.tempfilter = temp;
- Doc.setDocFilter(this.props.Document, this._key, temp, "check");
- this.props.col.setColor("green");
- this.closeResultsVisibility = "contents";
- }
- }
- else {
- Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove");
- this.updateFilter();
- if (this.showKeys.length) {
- this.onSelect(this.showKeys[0]);
- } else if (this._searchTerm !== "" && this.props.canAddNew) {
- this.setSearchTerm(this._searchTerm || this._key);
- this.onSelect(this._searchTerm);
- }
- }
- }
- }
-
- onChange = (val: string): void => {
- this.setSearchTerm(val);
- }
-
- @action
- onFocus = (e: React.FocusEvent): void => {
- this._isOpen = true;
- this.props.setIsEditing(true);
- }
-
- @computed get showKeys() {
- const whitelistKeys = ["context", "author", "*lastModified", "text", "data", "tags", "creationDate"];
- const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
- const showKeys = new Set<string>();
- [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.noviceMode ||
- whitelistKeys.includes(key)
- || ((!key.startsWith("_") && key[0] === key[0].toUpperCase()) || key[0] === "#")) ? showKeys.add(key) : null);
- return Array.from(showKeys.keys()).filter(key => !this._searchTerm || key.includes(this._searchTerm));
- }
-
- @computed get renderOptions() {
- if (!this._isOpen) {
- this.defaultMenuHeight = 0;
- return (null);
- }
- const options = this.showKeys.map(key => {
- return <div key={key} className="key-option" style={{
- border: "1px solid lightgray",
- width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white",
- }}
- onPointerDown={e => {
- e.stopPropagation();
- }}
- onClick={() => {
- this.onSelect(key);
- this.setSearchTerm("");
- }}>{key}</div>;
- });
-
- // if search term does not already exist as a group type, give option to create new group type
-
- if (this._key !== this._searchTerm.slice(0, this._key.length)) {
- if (this._searchTerm !== "" && this.props.canAddNew) {
- options.push(<div key={""} className="key-option" style={{
- border: "1px solid lightgray", width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white",
- }}
- onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}>
- Create "{this._searchTerm}" key</div>);
- }
- }
-
- if (options.length === 0) {
- this.defaultMenuHeight = 0;
- }
- else {
- if (this.props.docs) {
- const panesize = this.props.docs.length * 30;
- options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8;
- }
- else {
- options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8;
- }
- }
- return options;
- }
-
- @computed get docSafe() { return DocListCast(this.props.dataDoc?.[this.props.fieldKey]); }
-
- @computed get renderFilterOptions() {
- if (!this._isOpen || !this.props.dataDoc) {
- this.defaultMenuHeight = 0;
- return (null);
- }
- const keyOptions: string[] = [];
- const colpos = this._searchTerm.indexOf(":");
- const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length);
- this.docSafe.forEach(doc => {
- const key = StrCast(doc[this._key]);
- if (keyOptions.includes(key) === false && key.includes(temp) && key !== "") {
- keyOptions.push(key);
- }
- });
-
- const filters = StrListCast(this.props.Document._docFilters);
- if (filters.some(filter => filter.split(":")[0] === this._key) === false) {
- this.props.col.setColor("rgb(241, 239, 235)");
- this.closeResultsVisibility = "none";
- }
- for (let i = 0; i < (filters?.length ?? 0) - 1; i++) {
- if (filters[i] === this.props.col.heading && keyOptions.includes(filters[i].split(":")[1]) === false) {
- keyOptions.push(filters[i + 1]);
- }
- }
- const options = keyOptions.map(key => {
- let bool = false;
- if (filters !== undefined) {
- const ind = filters.findIndex(filter => filter.split(":")[1] === key);
- const fields = ind === -1 ? undefined : filters[ind].split(":");
- bool = fields ? fields[2] === "check" : false;
- }
- return <div key={key} className="key-option" style={{
- paddingLeft: 5, textAlign: "left",
- width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", backgroundColor: "white",
- }}
- >
- <input type="checkbox"
- onPointerDown={e => e.stopPropagation()}
- onClick={e => e.stopPropagation()}
- onChange={action(e => {
- if (e.target.checked) {
- Doc.setDocFilter(this.props.Document, this._key, key, "check");
- this.closeResultsVisibility = "contents";
- this.props.col.setColor("green");
- } else {
- Doc.setDocFilter(this.props.Document, this._key, key, "remove");
- this.updateFilter();
- }
- })}
- checked={bool}
- />
- <span style={{ paddingLeft: 4 }}>
- {key}
- </span>
-
- </div>;
- });
- if (options.length === 0) {
- this.defaultMenuHeight = 0;
- }
- else {
- if (this.props.docs) {
- const panesize = this.props.docs.length * 30;
- options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8;
- }
- else {
- options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8;
- }
-
- }
- return options;
- }
-
- @observable defaultMenuHeight = 0;
-
-
- updateFilter() {
- const filters = Cast(this.props.Document._docFilters, listSpec("string"));
- if (filters === undefined || filters.length === 0 || filters.some(filter => filter.split(":")[0] === this._key) === false) {
- this.props.col.setColor("rgb(241, 239, 235)");
- this.closeResultsVisibility = "none";
- }
- }
-
- @computed get scriptField() {
- const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
- const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
- return script ? () => script : undefined;
- }
- filterBackground = () => "rgba(105, 105, 105, 0.432)";
- @observable filterOpen: boolean | undefined = undefined;
- closeResultsVisibility: string = "none";
-
- removeFilters = (e: React.PointerEvent): void => {
- const keyOptions: string[] = [];
- this.docSafe.forEach(doc => {
- const key = StrCast(doc[this._key]);
- if (keyOptions.includes(key) === false) {
- keyOptions.push(key);
- }
- });
-
- Doc.setDocFilter(this.props.Document, this._key, "", "remove");
- this.props.col.setColor("rgb(241, 239, 235)");
- this.closeResultsVisibility = "none";
- }
- render() {
- return (
- <div style={{ display: "flex", width: '100%', alignContent: 'center', alignItems: 'center' }} ref={this.setNode}>
- <div className="schema-icon" onClick={e => { this.props.openHeader(this.props.col, e.clientX, e.clientY); e.stopPropagation(); }}>
- <FontAwesomeIcon icon={this.props.icon} size="lg" style={{ display: "inline" }} />
- </div>
-
- <div className="keys-dropdown" style={{ zIndex: 1, width: this.props.width, maxWidth: this.props.width }}>
- <input className="keys-search" style={{ width: "100%" }}
- ref={this._inputRef} type="text"
- value={this._searchTerm} placeholder="Column key"
- onKeyDown={this.onKeyDown}
- onChange={e => this.onChange(e.target.value)}
- onClick={(e) => { e.stopPropagation(); this._inputRef.current?.focus(); }}
- onFocus={this.onFocus} ></input>
- <div style={{ display: this.closeResultsVisibility }}>
- <FontAwesomeIcon onPointerDown={this.removeFilters} icon={"times-circle"} size="lg"
- style={{ cursor: "hand", color: "grey", padding: 2, left: -20, top: -1, height: 15, position: "relative" }} />
- </div>
- {!this._isOpen ? (null) : <div className="keys-options-wrapper" style={{
- width: this.props.width, maxWidth: this.props.width, height: "auto",
- }}>
- {this._searchTerm.includes(":") ? this.renderFilterOptions : this.renderOptions}
- </div>}
- </div >
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
deleted file mode 100644
index 28d2e6ab1..000000000
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import React = require('react');
-import { action } from 'mobx';
-import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { DragManager } from '../../../util/DragManager';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { Transform } from '../../../util/Transform';
-import './CollectionSchemaView.scss';
-
-export interface MovableColumnProps {
- columnRenderer: React.ReactNode;
- columnValue: SchemaHeaderField;
- allColumns: SchemaHeaderField[];
- reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columns: SchemaHeaderField[]) => void;
- ScreenToLocalTransform: () => Transform;
-}
-export class MovableColumn extends React.Component<MovableColumnProps> {
- // The header of the column
- private _header?: React.RefObject<HTMLDivElement> = React.createRef();
- // The container of the function that is responsible for moving the column over to a new plac
- private _colDropDisposer?: DragManager.DragDropDisposer;
- // initial column position
- private _startDragPosition: { x: number; y: number } = { x: 0, y: 0 };
- // sensitivity to being dragged, in pixels
- private _sensitivity: number = 16;
- // Column reference ID
- private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
-
- onPointerEnter = (e: React.PointerEvent): void => {
- // if the column is left-clicked and it is being dragged
- if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
- this._header!.current!.className = 'collectionSchema-col-wrapper';
- document.addEventListener('pointermove', this.onDragMove, true);
- }
- };
-
- onPointerLeave = (e: React.PointerEvent): void => {
- this._header!.current!.className = 'collectionSchema-col-wrapper';
- document.removeEventListener('pointermove', this.onDragMove, true);
- !e.buttons && document.removeEventListener('pointermove', this.onPointerMove);
- };
-
- onDragMove = (e: PointerEvent): void => {
- // only take into account the horizonal direction when a column is dragged
- const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
- const rect = this._header!.current!.getBoundingClientRect();
- // Now store the point at the top center of the column when it was in its original position
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top);
- // to be compared with its new horizontal position
- const before = x[0] < bounds[0];
- this._header!.current!.className = 'collectionSchema-col-wrapper';
- if (before) this._header!.current!.className += ' col-before';
- if (!before) this._header!.current!.className += ' col-after';
- e.stopPropagation();
- };
-
- createColDropTarget = (ele: HTMLDivElement) => {
- this._colDropDisposer?.();
- if (ele) {
- this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this));
- }
- };
-
- colDrop = (e: Event, de: DragManager.DropEvent) => {
- document.removeEventListener('pointermove', this.onDragMove, true);
- // we only care about whether the column is shifted to the side
- const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
- // get the dimensions of the smallest rectangle that bounds the header
- const rect = this._header!.current!.getBoundingClientRect();
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top);
- // get whether the column was dragged before or after where it is now
- const before = x[0] < bounds[0];
- const colDragData = de.complete.columnDragData;
- // if there is colDragData, which happen when the drag is complete, reorder the columns according to the established variables
- if (colDragData) {
- e.stopPropagation();
- this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns);
- return true;
- }
- return false;
- };
-
- onPointerMove = (e: PointerEvent) => {
- const onRowMove = (e: PointerEvent) => {
- e.stopPropagation();
- e.preventDefault();
-
- document.removeEventListener('pointermove', onRowMove);
- document.removeEventListener('pointerup', onRowUp);
- const dragData = new DragManager.ColumnDragData(this.props.columnValue);
- DragManager.StartColumnDrag(this._dragRef.current!, dragData, e.x, e.y);
- };
- const onRowUp = (): void => {
- document.removeEventListener('pointermove', onRowMove);
- document.removeEventListener('pointerup', onRowUp);
- };
- // if the left mouse button is the one being held
- if (e.buttons === 1) {
- const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y);
- // If the movemnt of the drag exceeds the sensitivity value
- if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
- document.removeEventListener('pointermove', this.onPointerMove);
- e.stopPropagation();
-
- document.addEventListener('pointermove', onRowMove);
- document.addEventListener('pointerup', onRowUp);
- }
- }
- };
-
- onPointerUp = (e: React.PointerEvent) => {
- document.removeEventListener('pointermove', this.onPointerMove);
- };
-
- @action
- onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => {
- this._dragRef = ref;
- const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY);
- // If the cell thing dragged is not being edited
- if (!(e.target as any)?.tagName.includes('INPUT')) {
- this._startDragPosition = { x: dx, y: dy };
- document.addEventListener('pointermove', this.onPointerMove);
- }
- };
-
- render() {
- const reference = React.createRef<HTMLDivElement>();
-
- return (
- <div className="collectionSchema-col" ref={this.createColDropTarget}>
- <div className="collectionSchema-col-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- <div className="col-dragger" ref={reference} onPointerDown={e => this.onPointerDown(e, reference)} onPointerUp={this.onPointerUp}>
- {this.props.columnRenderer}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
deleted file mode 100644
index 3cb2df7d3..000000000
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
+++ /dev/null
@@ -1,152 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action } from 'mobx';
-import * as React from 'react';
-import { ReactTableDefaults, RowInfo } from 'react-table';
-import { Doc } from '../../../../fields/Doc';
-import { Cast, FieldValue, StrCast } from '../../../../fields/Types';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType, SetupDrag } from '../../../util/DragManager';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { Transform } from '../../../util/Transform';
-import { undoBatch } from '../../../util/UndoManager';
-import { ContextMenu } from '../../ContextMenu';
-import { OpenWhere } from '../../nodes/DocumentView';
-import './CollectionSchemaView.scss';
-
-export interface MovableRowProps {
- rowInfo: RowInfo;
- ScreenToLocalTransform: () => Transform;
- addDoc: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean;
- removeDoc: (doc: Doc | Doc[]) => boolean;
- rowFocused: boolean;
- textWrapRow: (doc: Doc) => void;
- rowWrapped: boolean;
- dropAction: string;
- addDocTab: any;
-}
-
-export class MovableRow extends React.Component<React.PropsWithChildren<MovableRowProps>> {
- private _header?: React.RefObject<HTMLDivElement> = React.createRef();
- private _rowDropDisposer?: DragManager.DragDropDisposer;
-
- // Event listeners are only necessary when the user is hovering over the table
- // Create one when the mouse starts hovering...
- onPointerEnter = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
- this._header!.current!.className = 'collectionSchema-row-wrapper';
- document.addEventListener('pointermove', this.onDragMove, true);
- }
- };
- // ... and delete it when the mouse leaves
- onPointerLeave = (e: React.PointerEvent): void => {
- this._header!.current!.className = 'collectionSchema-row-wrapper';
- document.removeEventListener('pointermove', this.onDragMove, true);
- };
- // The method for the event listener, reorders columns when dragged to their new locations.
- onDragMove = (e: PointerEvent): void => {
- const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
- const rect = this._header!.current!.getBoundingClientRect();
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
- const before = x[1] < bounds[1];
- this._header!.current!.className = 'collectionSchema-row-wrapper';
- if (before) this._header!.current!.className += ' row-above';
- if (!before) this._header!.current!.className += ' row-below';
- e.stopPropagation();
- };
- componentWillUnmount() {
- this._rowDropDisposer?.();
- }
- //
- createRowDropTarget = (ele: HTMLDivElement) => {
- this._rowDropDisposer?.();
- if (ele) {
- this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
- }
- };
- // Controls what hppens when a row is dragged and dropped
- rowDrop = (e: Event, de: DragManager.DropEvent) => {
- this.onPointerLeave(e as any);
- const rowDoc = FieldValue(Cast(this.props.rowInfo.original, Doc));
- if (!rowDoc) return false;
-
- const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
- const rect = this._header!.current!.getBoundingClientRect();
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
- const before = x[1] < bounds[1];
-
- const docDragData = de.complete.docDragData;
- if (docDragData) {
- e.stopPropagation();
- if (docDragData.draggedDocuments[0] === rowDoc) return true;
- const addDocument = (doc: Doc | Doc[]) => this.props.addDoc(doc, rowDoc, before);
- const movedDocs = docDragData.draggedDocuments;
- return docDragData.dropAction || docDragData.userDropAction
- ? docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
- : docDragData.moveDocument
- ? movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false)
- : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
- }
- return false;
- };
-
- onRowContextMenu = (e: React.MouseEvent): void => {
- const description = this.props.rowWrapped ? 'Unwrap text on row' : 'Text wrap row';
- ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: 'file-pdf' });
- };
-
- @undoBatch
- @action
- move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => {
- const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
- return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
- };
-
- @action
- onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
- console.log('yes');
- if (e.key === 'Backspace' || e.key === 'Delete') {
- undoBatch(() => this.props.removeDoc(this.props.rowInfo.original));
- }
- };
-
- render() {
- const { children = null, rowInfo } = this.props;
-
- if (!rowInfo) {
- return <ReactTableDefaults.TrComponent>{children}</ReactTableDefaults.TrComponent>;
- }
-
- const { original } = rowInfo;
- const doc = FieldValue(Cast(original, Doc));
-
- if (!doc) return null;
-
- const reference = React.createRef<HTMLDivElement>();
- const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType);
-
- let className = 'collectionSchema-row';
- if (this.props.rowFocused) className += ' row-focused';
- if (this.props.rowWrapped) className += ' row-wrapped';
-
- return (
- <div className={className} onKeyPress={this.onKeyDown} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}>
- <div className="collectionSchema-row-wrapper" onKeyPress={this.onKeyDown} ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- <ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown}>
- <div className="row-dragger">
- <div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}>
- <FontAwesomeIcon icon="trash" size="sm" />
- </div>
- <div className="row-option" style={{ cursor: 'grab' }} ref={reference} onPointerDown={onItemDown}>
- <FontAwesomeIcon icon="grip-vertical" size="sm" />
- </div>
- <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, OpenWhere.addRight)}>
- <FontAwesomeIcon icon="external-link-alt" size="sm" />
- </div>
- </div>
- {children}
- </ReactTableDefaults.TrComponent>
- </div>
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 19401c7f0..1ef2fb4ef 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -1,599 +1,226 @@
@import '../../global/globalCssVariables.scss';
-@import '../../../../../node_modules/react-table/react-table.css';
-.collectionSchemaView-container {
- border-width: $COLLECTION_BORDER_WIDTH;
- border-color: $medium-gray;
- border-style: solid;
- border-radius: $border-radius;
- box-sizing: border-box;
- position: relative;
- top: 0;
- width: 100%;
- height: 100%;
- margin-top: 0;
- transition: top 0.5s;
- display: flex;
- justify-content: space-between;
- flex-wrap: nowrap;
- touch-action: none;
- div {
- touch-action: none;
- }
- .collectionSchemaView-tableContainer {
- width: 100%;
- height: 100%;
- }
- .collectionSchemaView-dividerDragger {
- position: relative;
- height: 100%;
- width: $SCHEMA_DIVIDER_WIDTH;
- z-index: 20;
- right: 0;
- top: 0;
- background: gray;
- cursor: col-resize;
- }
- // .documentView-node:first-child {
- // background: $white;
- // }
-}
-.collectionSchemaView-searchContainer {
- border-width: $COLLECTION_BORDER_WIDTH;
- border-color: $medium-gray;
- border-style: solid;
- border-radius: $border-radius;
- box-sizing: border-box;
- position: relative;
- top: 0;
- width: 100%;
+.collectionSchemaView {
+ cursor: default;
height: 100%;
- margin-top: 0;
- transition: top 0.5s;
display: flex;
- justify-content: space-between;
- flex-wrap: nowrap;
- touch-action: none;
- padding: 2px;
- div {
- touch-action: none;
- }
- .collectionSchemaView-tableContainer {
- width: 100%;
- height: 100%;
- }
- .collectionSchemaView-dividerDragger {
- position: relative;
- height: 100%;
- width: 20px;
- z-index: 20;
- right: 0;
- top: 0;
- background: gray;
- cursor: col-resize;
- }
- // .documentView-node:first-child {
- // background: $white;
- // }
-}
+ flex-direction: row;
+
+ .schema-table {
+ background-color: $white;
+ cursor: grab;
+ overflow: scroll;
+
+ .schema-column-menu,
+ .schema-filter-menu {
+ background: $light-gray;
+ position: absolute;
+ min-width: 200px;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ z-index: 1;
-.ReactTable {
- width: 100%;
- background: white;
- box-sizing: border-box;
- border: none !important;
- float: none !important;
- .rt-table {
- height: 100%;
- display: -webkit-inline-box;
- direction: ltr;
- overflow: visible;
- }
- .rt-noData {
- display: none;
- }
- .rt-thead {
- width: 100%;
- z-index: 100;
- overflow-y: visible;
- &.-header {
- font-size: 12px;
- height: 30px;
- box-shadow: none;
- z-index: 100;
- overflow-y: visible;
- }
- .rt-resizable-header-content {
- height: 100%;
- overflow: visible;
- }
- .rt-th {
- padding: 0;
- border-left: solid 1px $light-gray;
- }
- }
- .rt-th {
- font-size: 13px;
- text-align: center;
- &:last-child {
- overflow: visible;
- }
- }
- .rt-tbody {
- width: 100%;
- direction: rtl;
- overflow: visible;
- .rt-td {
- border-right: 1px solid rgba(0, 0, 0, 0.2);
- }
- }
- .rt-tr-group {
- direction: ltr;
- flex: 0 1 auto;
- min-height: 30px;
- border: 0 !important;
- }
- .rt-tr-group:nth-of-type(even) {
- direction: ltr;
- flex: 0 1 auto;
- min-height: 30px;
- border: 0 !important;
- background-color: red;
- }
- .rt-tr {
- width: 100%;
- min-height: 30px;
- }
- .rt-td {
- padding: 0;
- font-size: 13px;
- text-align: center;
- white-space: nowrap;
- display: flex;
- align-items: center;
- .imageBox-cont {
- position: relative;
- max-height: 100%;
- }
- .imageBox-cont img {
- object-fit: contain;
- max-width: 100%;
- height: 100%;
- }
- .videoBox-cont {
- object-fit: contain;
- width: auto;
- height: 100%;
- }
- }
- .rt-td.rt-expandable {
- display: flex;
- align-items: center;
- height: inherit;
- }
- .rt-resizer {
- width: 8px;
- right: -4px;
- }
- .rt-resizable-header {
- padding: 0;
- height: 30px;
- }
- .rt-resizable-header:last-child {
- overflow: visible;
- .rt-resizer {
- width: 5px !important;
- }
- }
-}
+ .schema-key-search-input {
+ width: calc(100% - 20px);
+ margin: 10px;
+ }
-.documentView-node-topmost {
- text-align: left;
- transform-origin: center top;
- display: inline-block;
-}
+ .schema-key-search-result {
+ cursor: pointer;
+ padding: 2px 10px;
+ width: 100%;
-.collectionSchema-col {
- height: 100%;
-}
+ &:hover {
+ background-color: $medium-gray;
+ }
+ }
-.collectionSchema-header-menu {
- height: auto;
- z-index: 100;
- position: absolute;
- background: white;
- padding: 5px;
- position: fixed;
- background: white;
- border: black 1px solid;
- .collectionSchema-header-toggler {
- z-index: 100;
- width: 100%;
- height: 100%;
- padding: 4px;
- letter-spacing: 2px;
- text-transform: uppercase;
- svg {
- margin-right: 4px;
- }
- }
-}
+ .schema-key-search,
+ .schema-new-key-options {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ }
-.collectionSchemaView-header {
- height: 100%;
- color: gray;
- z-index: 100;
- overflow-y: visible;
- display: flex;
- justify-content: space-between;
- flex-wrap: wrap;
-}
+ .schema-new-key-options {
+ margin: 10px;
+ .schema-key-warning {
+ color: red;
+ font-weight: normal;
+ align-self: center;
+ }
+ }
-button.add-column {
- width: 28px;
-}
+ .schema-key-list {
+ width: 100%;
+ max-height: 300px;
+ overflow-y: auto;
+ }
-.collectionSchemaView-menuOptions-wrapper {
- background: rgb(241, 239, 235);
- display: flex;
- cursor: default;
- height: 100%;
- align-content: center;
- align-items: center;
-}
+ .schema-key-type-option {
+ margin: 2px 0px;
-.collectionSchema-header-menuOptions {
- color: black;
- width: 180px;
- text-align: left;
- .collectionSchema-headerMenu-group {
- padding: 7px 0;
- border-bottom: 1px solid lightgray;
- cursor: pointer;
- &:first-child {
- padding-top: 0;
- }
- &:last-child {
- border: none;
- text-align: center;
- padding: 12px 0 0 0;
- }
- }
- label {
- color: $medium-gray;
- font-weight: normal;
- letter-spacing: 2px;
- text-transform: uppercase;
- }
- input {
- color: black;
- width: 100%;
- }
- .columnMenu-option {
- cursor: pointer;
- padding: 3px;
- background-color: white;
- transition: background-color 0.2s;
- &:hover {
- background-color: $light-gray;
- }
- &.active {
- font-weight: bold;
- border: 2px solid $light-gray;
- }
- svg {
- color: gray;
- margin-right: 5px;
- width: 10px;
- }
- }
+ input {
+ margin-right: 5px;
+ }
+ }
- .keys-dropdown {
- position: relative;
- //width: 100%;
- background-color: white;
- input {
- border: 2px solid $light-gray;
- padding: 3px;
- height: 28px;
- font-weight: bold;
- letter-spacing: '2px';
- text-transform: 'uppercase';
- &:focus {
- font-weight: normal;
+ .schema-key-default-val {
+ margin: 5px 0;
}
- }
- }
- .columnMenu-colors {
- display: flex;
- justify-content: space-between;
- flex-wrap: wrap;
- .columnMenu-colorPicker {
- cursor: pointer;
- width: 20px;
- height: 20px;
- border-radius: 10px;
- &.active {
- border: 2px solid white;
- box-shadow: 0 0 0 2px lightgray;
+
+ .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;
}
}
}
-}
-.schema-icon {
- cursor: pointer;
- width: 25px;
- height: 25px;
- display: flex;
- align-items: center;
- justify-content: center;
- align-content: center;
- background-color: $medium-blue;
- color: white;
- margin-right: 5px;
- font-size: 10px;
- border-radius: 3px;
-}
-
-.keys-options-wrapper {
- position: absolute;
- text-align: left;
- height: fit-content;
- top: 100%;
- z-index: 21;
- background-color: #ffffff;
- box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
- padding: 1px;
- .key-option {
- cursor: pointer;
- color: #000000;
- width: 100%;
- height: 25px;
- font-weight: 400;
- display: flex;
- justify-content: left;
- align-items: center;
- padding-left: 5px;
- &:hover {
- background-color: $light-gray;
- }
+ .schema-preview-divider {
+ height: 100%;
+ background: black;
+ cursor: ew-resize;
}
}
-.collectionSchema-row {
- height: 100%;
- background-color: white;
- &.row-focused .rt-td {
- background-color: $light-blue; //$light-gray;
- overflow: visible;
- }
- &.row-wrapped {
- .rt-td {
- white-space: normal;
- }
- }
- .row-dragger {
+.schema-header-row {
+ cursor: grab;
+ justify-content: flex-end;
+
+ .row-menu {
display: flex;
- justify-content: space-evenly;
- width: 58px;
- position: absolute;
- /* max-width: 50px; */
- min-height: 30px;
- align-items: center;
- color: lightgray;
- background-color: white;
- transition: color 0.1s ease;
- .row-option {
- color: black;
- cursor: pointer;
- position: relative;
- transition: color 0.1s ease;
- display: flex;
- flex-direction: column;
- justify-content: center;
- z-index: 2;
- border-radius: 3px;
- padding: 3px;
- &:hover {
- background-color: $light-gray;
- }
- }
- }
- .collectionSchema-row-wrapper {
- &.row-above {
- border-top: 1px solid $medium-blue;
- }
- &.row-below {
- border-bottom: 1px solid $medium-blue;
- }
- &.row-inside {
- border: 2px dashed $medium-blue;
- }
- .row-dragging {
- background-color: blue;
- }
+ justify-content: flex-end;
}
}
-.collectionSchemaView-cellContainer {
- width: 100%;
- height: unset;
-}
-
-.collectionSchemaView-cellContents {
- width: 100%;
-}
-
-.collectionSchemaView-cellWrapper {
+.schema-column-header {
+ background-color: $light-gray;
+ font-weight: bold;
display: flex;
- height: 100%;
- text-align: left;
- padding-left: 19px;
- position: relative;
+ flex-direction: row;
+ justify-content: space-between;
align-items: center;
- align-content: center;
- &:focus {
- outline: none;
- }
- &.editing {
- padding: 0;
- box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
- transform: scale(1.1);
- z-index: 40;
- input {
- outline: 0;
- border: none;
- background-color: $white;
- width: 100%;
- height: fit-content;
- min-height: 26px;
- }
- }
- &.focused {
+ padding: 0;
+ z-index: 1;
+ border: 1px solid $medium-gray;
+ //overflow: hidden;
+
+ .schema-column-title {
+ flex-grow: 2;
+ margin: 5px;
overflow: hidden;
- &.inactive {
- border: none;
- }
- }
- p {
- width: 100%;
- height: 100%;
- }
- &:hover .collectionSchemaView-cellContents-docExpander {
- display: block;
- }
- .collectionSchemaView-cellContents-document {
- display: inline-block;
+ min-width: 20%;
}
- .collectionSchemaView-cellContents-docButton {
- float: right;
- width: '15px';
- height: '15px';
+
+ .schema-header-menu {
+ margin: 5px;
}
- .collectionSchemaView-dropdownWrapper {
- border: grey;
- border-style: solid;
- border-width: 1px;
- height: 30px;
- .collectionSchemaView-dropdownButton {
- //display: inline-block;
- float: left;
- height: 100%;
- }
- .collectionSchemaView-dropdownText {
- display: inline-block;
- //float: right;
- height: 100%;
- display: 'flex';
- font-size: 13;
- justify-content: 'center';
- align-items: 'center';
+
+ .schema-column-resizer {
+ height: 100%;
+ width: 3px;
+ cursor: ew-resize;
+
+ &:hover {
+ background-color: $light-blue;
}
}
- .collectionSchemaView-dropdownContainer {
- position: absolute;
- border: 1px solid rgba(0, 0, 0, 0.04);
- box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14);
- .collectionSchemaView-dropdownOption:hover {
- background-color: rgba(0, 0, 0, 0.14);
- cursor: pointer;
- }
+
+ .schema-column-resizer.left {
+ min-width: 5px;
+ transform: translate(-3px, 0px);
+ align-self: flex-start;
+ background-color: $medium-gray;
}
}
-.collectionSchemaView-cellContents-docExpander {
- height: 30px;
- width: 30px;
- display: none;
- position: absolute;
- top: 0;
- right: 0;
- background-color: lightgray;
+.schema-header-menu {
+ display: flex;
+ flex-direction: row;
}
-.doc-drag-over {
- background-color: red;
+.schema-row-wrapper {
+ overflow: hidden;
}
-.collectionSchemaView-toolbar {
- z-index: 100;
+.schema-header-row {
+ background-color: $light-gray;
+ overflow: hidden;
}
-.collectionSchemaView-toolbar {
- height: 30px;
+.schema-header-row,
+.schema-row {
display: flex;
- justify-content: flex-end;
- padding: 0 10px;
- border-bottom: 2px solid gray;
- .collectionSchemaView-toolbar-item {
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
+ flex-direction: row;
+ height: 100%;
+ overflow: auto;
}
-#preview-schema-checkbox-div {
- margin-left: 20px;
- font-size: 12px;
+.schema-header-row > .schema-column-header:nth-child(2) > .left {
+ display: none;
}
-.collectionSchemaView-table {
- width: 100%;
- height: 100%;
- overflow: auto;
- padding: 3px;
+.schema-table-cell,
+.row-menu {
+ border: 1px solid $medium-gray;
+ overflow: hidden;
+ padding: 5px;
}
-.rt-td.rt-expandable {
- overflow: visible;
- position: relative;
- height: 100%;
- z-index: 1;
-}
+.schema-row {
+ cursor: grab;
+ justify-content: flex-end;
+ background: white;
-.reactTable-sub {
- background-color: rgb(252, 252, 252);
- width: 100%;
- .rt-thead {
- display: none;
- }
- .row-dragger {
- background-color: rgb(252, 252, 252);
- }
- .rt-table {
- background-color: rgb(252, 252, 252);
+ .row-menu {
+ display: flex;
+ flex-direction: row;
+ min-width: 50px;
+ justify-content: flex-end;
}
- .collectionSchemaView-table {
- width: 100%;
- border: solid 1px;
- overflow: visible;
- padding: 0px;
+
+ .row-cells {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
}
}
-.collectionSchemaView-expander {
- height: 100%;
- min-height: 30px;
- position: absolute;
- color: gray;
- width: 20;
- height: auto;
- left: 55;
+.schema-row-button,
+.schema-header-button {
+ color: $dark-gray;
+ margin: 3px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
svg {
- position: absolute;
- top: 50%;
- left: 10;
- transform: translate(-50%, -50%);
+ width: 15px;
}
}
-.collectionSchemaView-addRow {
- color: gray;
- letter-spacing: 2px;
- text-transform: uppercase;
+.schema-sort-button {
+ width: 17px;
+ height: 17px;
+ border-radius: 30%;
+ background-color: $dark-gray;
+ color: white;
+ margin: 3px;
cursor: pointer;
- font-size: 10.5px;
- margin-left: 50px;
- margin-top: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ svg {
+ width: 12px;
+ }
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index c4ee1805f..6d5a73e55 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,108 +1,66 @@
import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked } from 'mobx';
+import { action, computed, observable, ObservableMap, untracked } from 'mobx';
import { observer } from 'mobx-react';
-import Measure from 'react-measure';
-import { Resize } from 'react-table';
-import { Doc, Opt } from '../../../../fields/Doc';
+import { computedFn } from 'mobx-utils';
+import { Doc, Field, StrListCast } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
-import { PastelSchemaPalette, SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast, NumCast } from '../../../../fields/Types';
-import { TraceMobx } from '../../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
-import { DocUtils } from '../../../documents/Documents';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { DragManager } from '../../../util/DragManager';
import { SelectionManager } from '../../../util/SelectionManager';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
-import { ContextMenuProps } from '../../ContextMenuItem';
-import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
-import { DocumentView } from '../../nodes/DocumentView';
+import { EditableView } from '../../EditableView';
+import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { KeyValueBox } from '../../nodes/KeyValueBox';
import { DefaultStyleProvider } from '../../StyleProvider';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionSchemaView.scss';
-import { SchemaTable } from './SchemaTable';
-// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
+import { SchemaColumnHeader } from './SchemaColumnHeader';
+import { SchemaRowBox } from './SchemaRowBox';
export enum ColumnType {
- Any,
Number,
String,
Boolean,
Doc,
Image,
- List,
- Date,
}
-// this map should be used for keys that should have a const type of value
-const columnTypes: Map<string, ColumnType> = new Map([
- ['title', ColumnType.String],
- ['x', ColumnType.Number],
- ['y', ColumnType.Number],
- ['_width', ColumnType.Number],
- ['_height', ColumnType.Number],
- ['_nativeWidth', ColumnType.Number],
- ['_nativeHeight', ColumnType.Number],
- ['isPrototype', ColumnType.Boolean],
- ['_curPage', ColumnType.Number],
- ['_currentTimecode', ColumnType.Number],
- ['zIndex', ColumnType.Number],
-]);
+
+const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate', 'text'];
@observer
export class CollectionSchemaView extends CollectionSubView() {
- private _previewCont?: HTMLDivElement;
-
- @observable _previewDoc: Doc | undefined = undefined;
- @observable _focusedTable: Doc = this.props.Document;
- @observable _col: any = '';
- @observable _menuWidth = 0;
- @observable _headerOpen = false;
- @observable _headerIsEditing = false;
- @observable _menuHeight = 0;
- @observable _pointerX = 0;
- @observable _pointerY = 0;
- @observable _openTypes: boolean = false;
+ private _closestDropIndex: number = 0;
+ private _previewRef: HTMLDivElement | null = null;
+ private _makeNewColumn: boolean = false;
- @computed get previewWidth() {
- return () => NumCast(this.props.Document.schemaPreviewWidth);
- }
- @computed get previewHeight() {
- return () => this.props.PanelHeight() - 2 * this.borderWidth;
- }
- @computed get tableWidth() {
- return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth();
- }
- @computed get borderWidth() {
- return Number(COLLECTION_BORDER_WIDTH);
- }
- @computed get scale() {
- return this.props.ScreenToLocalTransform().Scale;
- }
- @computed get columns() {
- return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
- }
- set columns(columns: SchemaHeaderField[]) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns);
- }
+ public static _rowHeight: number = 40;
+ public static _minColWidth: number = 25;
+ public static _rowMenuWidth: number = 60;
+ public static _previewDividerWidth: number = 4;
- @computed get menuCoordinates() {
- let searchx = 0;
- let searchy = 0;
- if (this.props.Document._searchDoc) {
- const el = document.getElementsByClassName('collectionSchemaView-searchContainer')[0];
- if (el !== undefined) {
- const rect = el.getBoundingClientRect();
- searchx = rect.x;
- searchy = rect.y;
- }
- }
- const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx;
- const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy;
- return this.props.ScreenToLocalTransform().transformPoint(x, y);
+ @computed get _selectedDocs() {
+ return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.props.Document));
}
+ @observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
+ @observable _colEles: HTMLDivElement[] = [];
+ @observable _displayColumnWidths: number[] | undefined;
+ @observable _columnMenuIndex: number | undefined;
+ @observable _menuOptions: string[] = [];
+ @observable _newFieldWarning: string = '';
+ @observable _makeNewField: boolean = false;
+ @observable _newFieldDefault: any = 0;
+ @observable _newFieldType: ColumnType = ColumnType.Number;
+ @observable _menuValue: string = '';
+ @observable _filterColumnIndex: number | undefined;
+ @observable _filterValue: string = '';
get documentKeys() {
const docs = this.childDocs;
@@ -115,532 +73,807 @@ export class CollectionSchemaView extends CollectionSubView() {
//TODO Types
untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false)))));
- this.columns.forEach(key => (keys[key.heading] = true));
+ // this.columns.forEach(key => (keys[key.heading] = true));
return Array.from(Object.keys(keys));
}
- @action setHeaderIsEditing = (isEditing: boolean) => (this._headerIsEditing = isEditing);
+ @computed get previewWidth() {
+ return NumCast(this.layoutDoc.schemaPreviewWidth);
+ }
- @undoBatch
- setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => {
- this._openTypes = false;
- if (columnTypes.get(columnField.heading)) return;
-
- const columns = this.columns;
- const index = columns.indexOf(columnField);
- if (index > -1) {
- columnField.setType(NumCast(type));
- columns[index] = columnField;
- this.columns = columns;
+ @computed get tableWidth() {
+ return this.props.PanelWidth() - this.previewWidth - (this.previewWidth === 0 ? 0 : CollectionSchemaView._previewDividerWidth);
+ }
+
+ @computed get columnKeys() {
+ return Cast(this.layoutDoc.columnKeys, listSpec('string'), defaultColumnKeys);
+ }
+
+ @computed get storedColumnWidths() {
+ let widths = Cast(
+ this.layoutDoc.columnWidths,
+ listSpec('number'),
+ this.columnKeys.map(() => (this.tableWidth - CollectionSchemaView._rowMenuWidth) / this.columnKeys.length)
+ );
+
+ const totalWidth = widths.reduce((sum, width) => sum + width, 0);
+ if (totalWidth !== this.tableWidth - CollectionSchemaView._rowMenuWidth) {
+ widths = widths.map(w => {
+ const proportion = w / totalWidth;
+ return proportion * (this.tableWidth - CollectionSchemaView._rowMenuWidth);
+ });
}
- });
- @undoBatch
- setColumnColor = (columnField: SchemaHeaderField, color: string): void => {
- const columns = this.columns;
- const index = columns.indexOf(columnField);
- if (index > -1) {
- columnField.setColor(color);
- columns[index] = columnField;
- this.columns = columns; // need to set the columns to trigger rerender
+ return widths;
+ }
+
+ @computed get displayColumnWidths() {
+ return this._displayColumnWidths ?? this.storedColumnWidths;
+ }
+
+ @computed get sortField() {
+ return StrCast(this.layoutDoc.sortField);
+ }
+
+ @computed get sortDesc() {
+ return BoolCast(this.layoutDoc.sortDesc);
+ }
+
+ rowIndex(doc: Doc) {
+ return this.childDocs.indexOf(doc);
+ }
+
+ componentDidMount() {
+ this.props.setContentView?.(this);
+ document.addEventListener('keydown', this.onKeyDown);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('keydown', this.onKeyDown);
+ }
+
+ @action
+ onKeyDown = (e: KeyboardEvent) => {
+ if (this._selectedDocs.length > 0) {
+ switch (e.key) {
+ case 'ArrowDown':
+ {
+ const lastDoc = this._selectedDocs.lastElement();
+ const lastIndex = this.rowIndex(lastDoc);
+ const curDoc = this.childDocs[lastIndex];
+ if (lastIndex >= 0 && lastIndex < this.childDocs.length - 1) {
+ !e.shiftKey && this.clearSelection();
+ const newDoc = this.childDocs[lastIndex + 1];
+ if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+ else this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
+ }
+ e.stopPropagation();
+ }
+ break;
+ case 'ArrowUp':
+ {
+ const firstDoc = this._selectedDocs.lastElement();
+ const firstIndex = this.rowIndex(firstDoc);
+ const curDoc = this.childDocs[firstIndex];
+ if (firstIndex > 0 && firstIndex < this.childDocs.length) {
+ !e.shiftKey && this.clearSelection();
+ const newDoc = this.childDocs[firstIndex - 1];
+ if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+ else this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
+ }
+ e.stopPropagation();
+ }
+ break;
+ }
}
};
@undoBatch
@action
- setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => {
- const columns = this.columns;
- columns.forEach(col => col.setDesc(undefined));
-
- const index = columns.findIndex(c => c.heading === columnField.heading);
- const column = columns[index];
- column.setDesc(descending);
- columns[index] = column;
- this.columns = columns;
+ setSort = (field: string | undefined, desc: boolean = false) => {
+ this.layoutDoc.sortField = field;
+ this.layoutDoc.sortDesc = desc;
};
- renderTypes = (col: any) => {
- if (columnTypes.get(col.heading)) return null;
+ addRow = (doc: Doc | Doc[]) => {
+ const result: boolean = this.addDocument(doc);
+ this.setSort(this.sortField, this.sortDesc);
+ return result;
+ };
- const type = col.type;
+ @undoBatch
+ @action
+ changeColumnKey = (index: number, newKey: string, defaultVal?: any) => {
+ if (!this.documentKeys.includes(newKey)) {
+ this.addNewKey(newKey, defaultVal);
+ }
- const anyType = (
- <div className={'columnMenu-option' + (type === ColumnType.Any ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Any)}>
- <FontAwesomeIcon icon={'align-justify'} size="sm" />
- Any
- </div>
- );
+ let currKeys = [...this.columnKeys];
+ currKeys[index] = newKey;
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
+ };
- const numType = (
- <div className={'columnMenu-option' + (type === ColumnType.Number ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Number)}>
- <FontAwesomeIcon icon={'hashtag'} size="sm" />
- Number
- </div>
- );
+ @undoBatch
+ @action
+ addColumn = (key: string, defaultVal?: any) => {
+ if (!this.documentKeys.includes(key)) {
+ this.addNewKey(key, defaultVal);
+ }
- const textType = (
- <div className={'columnMenu-option' + (type === ColumnType.String ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.String)}>
- <FontAwesomeIcon icon={'font'} size="sm" />
- Text
- </div>
- );
+ const newColWidth = this.tableWidth / (this.storedColumnWidths.length + 1);
+ const currWidths = this.storedColumnWidths.slice();
+ currWidths.splice(0, 0, newColWidth);
+ const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0);
+ this.layoutDoc.columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)));
- const boolType = (
- <div className={'columnMenu-option' + (type === ColumnType.Boolean ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Boolean)}>
- <FontAwesomeIcon icon={'check-square'} size="sm" />
- Checkbox
- </div>
- );
+ let currKeys = this.columnKeys.slice();
+ currKeys.splice(0, 0, key);
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
+ };
- const listType = (
- <div className={'columnMenu-option' + (type === ColumnType.List ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.List)}>
- <FontAwesomeIcon icon={'list-ul'} size="sm" />
- List
- </div>
- );
+ @action
+ addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal));
- const docType = (
- <div className={'columnMenu-option' + (type === ColumnType.Doc ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Doc)}>
- <FontAwesomeIcon icon={'file'} size="sm" />
- Document
- </div>
- );
+ @undoBatch
+ @action
+ removeColumn = (index: number) => {
+ if (this.columnKeys.length === 1) return;
+ const currWidths = this.storedColumnWidths.slice();
+ currWidths.splice(index, 1);
+ const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0);
+ this.layoutDoc.columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)));
+
+ let currKeys = this.columnKeys.slice();
+ currKeys.splice(index, 1);
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
+ };
- const imageType = (
- <div className={'columnMenu-option' + (type === ColumnType.Image ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Image)}>
- <FontAwesomeIcon icon={'image'} size="sm" />
- Image
- </div>
- );
+ @action
+ startResize = (e: any, index: number) => {
+ this._displayColumnWidths = this.storedColumnWidths;
+ setupMoveUpEvents(this, e, (e, delta) => this.resizeColumn(e, index), this.finishResize, emptyFunction);
+ };
- const dateType = (
- <div className={'columnMenu-option' + (type === ColumnType.Date ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Date)}>
- <FontAwesomeIcon icon={'calendar'} size="sm" />
- Date
- </div>
- );
+ @action
+ resizeColumn = (e: PointerEvent, index: number) => {
+ if (this._displayColumnWidths) {
+ let shrinking;
+ let growing;
- const allColumnTypes = (
- <div className="columnMenu-types">
- {anyType}
- {numType}
- {textType}
- {boolType}
- {listType}
- {docType}
- {imageType}
- {dateType}
- </div>
- );
+ let change = e.movementX;
+
+ if (index !== 0) {
+ growing = change < 0 ? index : index - 1;
+ shrinking = change < 0 ? index - 1 : index;
+ }
- const justColType =
- type === ColumnType.Any
- ? anyType
- : type === ColumnType.Number
- ? numType
- : type === ColumnType.String
- ? textType
- : type === ColumnType.Boolean
- ? boolType
- : type === ColumnType.List
- ? listType
- : type === ColumnType.Doc
- ? docType
- : type === ColumnType.Date
- ? dateType
- : imageType;
+ if (shrinking === undefined || growing === undefined) return true;
- return (
- <div className="collectionSchema-headerMenu-group" onClick={action(() => (this._openTypes = !this._openTypes))}>
- <div>
- <label style={{ cursor: 'pointer' }}>Column type:</label>
- <FontAwesomeIcon icon={'caret-down'} size="lg" style={{ float: 'right', transform: `rotate(${this._openTypes ? '180deg' : 0})`, transition: '0.2s all ease' }} />
- </div>
- {this._openTypes ? allColumnTypes : justColType}
- </div>
- );
+ change = Math.abs(change);
+ if (this._displayColumnWidths[shrinking] - change < CollectionSchemaView._minColWidth) {
+ change = this._displayColumnWidths[shrinking] - CollectionSchemaView._minColWidth;
+ }
+
+ this._displayColumnWidths[shrinking] -= change * this.props.ScreenToLocalTransform().Scale;
+ this._displayColumnWidths[growing] += change * this.props.ScreenToLocalTransform().Scale;
+
+ return false;
+ }
+ return true;
};
- renderSorting = (col: any) => {
- const sort = col.desc;
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Sort by:</label>
- <div className="columnMenu-sort">
- <div className={'columnMenu-option' + (sort === true ? ' active' : '')} onClick={() => this.setColumnSort(col, true)}>
- <FontAwesomeIcon icon="sort-amount-down" size="sm" />
- Sort descending
- </div>
- <div className={'columnMenu-option' + (sort === false ? ' active' : '')} onClick={() => this.setColumnSort(col, false)}>
- <FontAwesomeIcon icon="sort-amount-up" size="sm" />
- Sort ascending
- </div>
- <div className="columnMenu-option" onClick={() => this.setColumnSort(col, undefined)}>
- <FontAwesomeIcon icon="times" size="sm" />
- Clear sorting
- </div>
- </div>
- </div>
- );
+ @action
+ finishResize = () => {
+ this.layoutDoc.columnWidths = new List<number>(this._displayColumnWidths);
+ this._displayColumnWidths = undefined;
};
- renderColors = (col: any) => {
- const selected = col.color;
+ @undoBatch
+ @action
+ swapColumns = (index1: number, index2: number) => {
+ const tempKey = this.columnKeys[index1];
+ const tempWidth = this.storedColumnWidths[index1];
+
+ let currKeys = this.columnKeys;
+ currKeys[index1] = currKeys[index2];
+ currKeys[index2] = tempKey;
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
+
+ let currWidths = this.storedColumnWidths;
+ currWidths[index1] = currWidths[index2];
+ currWidths[index2] = tempWidth;
+ this.layoutDoc.columnWidths = new List<number>(currWidths);
+ };
- const pink = PastelSchemaPalette.get('pink2');
- const purple = PastelSchemaPalette.get('purple2');
- const blue = PastelSchemaPalette.get('bluegreen1');
- const yellow = PastelSchemaPalette.get('yellow4');
- const red = PastelSchemaPalette.get('red2');
- const gray = '#f1efeb';
+ @action
+ dragColumn = (e: PointerEvent, index: number) => {
+ const dragData = new DragManager.ColumnDragData(index);
+ const dragEles = [this._colEles[index]];
+ this.childDocs.forEach(doc => {
+ dragEles.push(this._rowEles.get(doc).children[1].children[index]);
+ });
+ DragManager.StartColumnDrag(dragEles, dragData, e.x, e.y);
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Color:</label>
- <div className="columnMenu-colors">
- <div className={'columnMenu-colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.setColumnColor(col, pink!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.setColumnColor(col, purple!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.setColumnColor(col, blue!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.setColumnColor(col, yellow!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.setColumnColor(col, red!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.setColumnColor(col, gray)}></div>
- </div>
- </div>
- );
+ return true;
};
- @undoBatch
@action
- changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => {
- const columns = this.columns;
- if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, 'f1efeb')]);
+ addRowRef = (doc: Doc, ref: HTMLDivElement) => this._rowEles.set(doc, ref);
+
+ @action
+ setColRef = (index: number, ref: HTMLDivElement) => {
+ if (this._colEles.length <= index) {
+ this._colEles.push(ref);
} else {
- if (addNew) {
- columns.push(new SchemaHeaderField(newKey, 'f1efeb'));
- this.columns = columns;
- } else {
- const index = columns.map(c => c.heading).indexOf(oldKey);
- if (index > -1) {
- const column = columns[index];
- column.setHeading(newKey);
- columns[index] = column;
- this.columns = columns;
- if (filter) {
- Doc.setDocFilter(this.props.Document, newKey, filter, 'match');
- } else {
- this.props.Document._docFilters = undefined;
- }
- }
- }
+ this._colEles[index] = ref;
}
};
@action
- openHeader = (col: any, screenx: number, screeny: number) => {
- this._col = col;
- this._headerOpen = true;
- this._pointerX = screenx;
- this._pointerY = screeny;
+ addDocToSelection = (doc: Doc, extendSelection: boolean, index: number) => {
+ const rowDocView = DocumentManager.Instance.getDocumentView(doc);
+ if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection);
};
@action
- closeHeader = () => {
- this._headerOpen = false;
+ clearSelection = () => SelectionManager.DeselectAll();
+
+ selectRows = (rootDoc: Doc, lastSelected: Doc) => {
+ const index = this.childDocs.indexOf(rootDoc);
+ const lastSelectedRow = this.childDocs.indexOf(lastSelected);
+ const startRow = Math.min(lastSelectedRow, index);
+ const endRow = Math.max(lastSelectedRow, index);
+ for (let i = startRow; i <= endRow; i++) {
+ const currDoc = this.childDocs[i];
+ if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true, i);
+ }
};
- @undoBatch
+ sortedSelectedDocs = () => this.childDocs.filter(doc => this._selectedDocs.includes(doc));
+
+ setDropIndex = (index: number) => (this._closestDropIndex = index);
+
@action
- deleteColumn = (key: string) => {
- const columns = this.columns;
- if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([]);
- } else {
- const index = columns.map(c => c.heading).indexOf(key);
- if (index > -1) {
- columns.splice(index, 1);
- this.columns = columns;
- }
+ onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.complete.columnDragData) {
+ e.stopPropagation();
+ const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
+ let i = de.complete.columnDragData.colIndex;
+ this.displayColumnWidths.reduce((total, curr, index) => {
+ if (total <= mouseX && total + curr >= mouseX) {
+ i = index;
+ }
+ return total + curr;
+ }, CollectionSchemaView._rowMenuWidth);
+ this.swapColumns(de.complete.columnDragData.colIndex, i);
+ e.stopPropagation();
+ return true;
}
- this.closeHeader();
+ const draggedDocs = de.complete.docDragData?.draggedDocuments;
+ if (draggedDocs && super.onInternalDrop(e, de)) {
+ const pushedDocs = this.childDocs.filter((doc, index) => index >= this._closestDropIndex && !draggedDocs.includes(doc));
+ const pushedAndDraggedDocs = [...pushedDocs, ...draggedDocs];
+ const removed = this.childDocs.slice().filter(doc => !pushedAndDraggedDocs.includes(doc));
+ this.dataDoc[this.fieldKey ?? 'data'] = new List<Doc>([...removed, ...draggedDocs, ...pushedDocs]);
+ this.setSort(undefined);
+ SelectionManager.DeselectAll();
+ draggedDocs.forEach(doc => {
+ const draggedView = DocumentManager.Instance.getFirstDocumentView(doc);
+ if (draggedView) DocumentManager.Instance.RemoveView(draggedView);
+ DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true));
+ });
+ e.stopPropagation();
+ return true;
+ }
+ return false;
};
- getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(-this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, -this.borderWidth);
+ @action
+ onExternalDrop = async (e: React.DragEvent): Promise<void> => {
+ super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc)))));
+ this.setSort(undefined);
};
+ onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction);
+
@action
- onHeaderClick = (e: React.PointerEvent) => {
- e.stopPropagation();
+ onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ const nativeWidth = this._previewRef!.getBoundingClientRect();
+ const minWidth = 40;
+ const maxWidth = 1000;
+ const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
+ const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
+ this.layoutDoc.schemaPreviewWidth = width;
+ return false;
};
@action
- onWheel(e: React.WheelEvent) {
- const scale = this.props.ScreenToLocalTransform().Scale;
- this.props.isContentActive(true) && e.stopPropagation();
- }
+ addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
+ if (!value && !forceEmptyNote) return false;
+ const newDoc = Docs.Create.TextDocument(value, { title: value, _autoHeight: true });
+ FormattedTextBox.SelectOnLoad = newDoc[Id];
+ FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
+ return this.addRow(newDoc) || false;
+ };
- @computed get renderMenuContent() {
- TraceMobx();
- return (
- <div className="collectionSchema-header-menuOptions">
- {this.renderTypes(this._col)}
- {this.renderColors(this._col)}
- <div className="collectionSchema-headerMenu-group">
- <button
- onClick={() => {
- this.deleteColumn(this._col.heading);
- }}>
- Hide Column
- </button>
- </div>
- </div>
- );
- }
+ menuCallback = (x: number, y: number) => {
+ ContextMenu.Instance.clearItems();
- private createTarget = (ele: HTMLDivElement) => {
- this._previewCont = ele;
- super.CreateDropTarget(ele);
+ DocUtils.addDocumentCreatorMenuItems(doc => this.addRow(doc), this.addRow, x, y, true);
+
+ ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
+ Doc.GetProto(this.props.Document)[name] = '';
+ this.addRow(Docs.Create.TextDocument('', { title: name, _autoHeight: true }));
+ });
+ ContextMenu.Instance.displayMenu(x, y, undefined, true);
};
- isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable;
+ focusDocument = (doc: Doc, options: DocFocusOptions) => {
+ Doc.BrushDoc(doc);
+
+ const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
+ if (found) {
+ const top = found.getBoundingClientRect().top;
+ const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
+ if (Math.floor(localTop[1]) !== 0) {
+ let focusSpeed = options.zoomTime ?? 500;
+ smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
+ return focusSpeed;
+ }
+ }
+ return undefined;
+ };
- @action setFocused = (doc: Doc) => (this._focusedTable = doc);
+ @computed get fieldDefaultInput() {
+ switch (this._newFieldType) {
+ case ColumnType.Number:
+ return <input type="number" name="" id="" value={this._newFieldDefault ?? 0} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+ case ColumnType.Boolean:
+ return (
+ <>
+ <input type="checkbox" name="" id="" value={this._newFieldDefault} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.checked))} />
+ {this._newFieldDefault ? 'true' : 'false'}
+ </>
+ );
+ case ColumnType.String:
+ return <input type="text" name="" id="" value={this._newFieldDefault ?? ''} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+ }
+ }
- @action setPreviewDoc = (doc: Opt<Doc>) => {
- SelectionManager.SelectSchemaViewDoc(doc);
- this._previewDoc = doc;
+ onSearchKeyDown = (e: React.KeyboardEvent) => {
+ switch (e.key) {
+ case 'Enter':
+ this._menuOptions.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuOptions[0]) : action(() => (this._makeNewField = true))();
+ break;
+ case 'Escape':
+ this.closeColumnMenu();
+ break;
+ }
};
- //toggles preview side-panel of schema
@action
- toggleExpander = () => {
- this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0;
+ setKey = (key: string, defaultVal?: any) => {
+ if (this._makeNewColumn) {
+ this.addColumn(key, defaultVal);
+ } else {
+ this.changeColumnKey(this._columnMenuIndex!, key, defaultVal);
+ }
+ this.closeColumnMenu();
};
- onDividerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander);
+ setColumnValues = (key: string, value: string) => {
+ let success: boolean = true;
+ this.childDocs.forEach(doc => success && KeyValueBox.SetField(doc, key, value));
+ return success;
};
+
@action
- onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
- const nativeWidth = this._previewCont!.getBoundingClientRect();
- const minWidth = 40;
- const maxWidth = 1000;
- const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
- const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
- this.props.Document.schemaPreviewWidth = width;
- return false;
+ openColumnMenu = (index: number, newCol: boolean) => {
+ this._makeNewColumn = false;
+ this._columnMenuIndex = index;
+ this._menuValue = '';
+ this._menuOptions = this.documentKeys;
+ this._makeNewField = false;
+ this._newFieldWarning = '';
+ this._makeNewField = false;
+ this._makeNewColumn = newCol;
+ };
+
+ @action
+ closeColumnMenu = () => (this._columnMenuIndex = undefined);
+
+ @action
+ openFilterMenu = (index: number) => {
+ this._filterColumnIndex = index;
+ this._filterValue = this.getFieldFilters(this.columnKeys[this._filterColumnIndex!]).map(filter => filter.split(':')[1])[0];
};
- onPointerDown = (e: React.PointerEvent): void => {
- if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
- if (this.props.isSelected(true)) e.stopPropagation();
- else this.props.select(false);
+ @action
+ closeFilterMenu = (setValue: boolean) => {
+ if (setValue) {
+ if (this._filterValue !== '') {
+ Doc.setDocFilter(this.Document, this.columnKeys[this._filterColumnIndex!], this._filterValue, 'check', false, undefined, false);
+ } else {
+ this.removeFieldFilters(this.columnKeys[this._filterColumnIndex!]);
+ }
}
+ this._filterColumnIndex = undefined;
};
- @computed
- get previewDocument(): Doc | undefined {
- return this._previewDoc;
- }
+ openContextMenu = (x: number, y: number, index: number) => {
+ this.closeColumnMenu();
+ this.closeFilterMenu(false);
+ ContextMenu.Instance.clearItems();
+ ContextMenu.Instance.addItem({
+ description: 'Change field',
+ event: () => this.openColumnMenu(index, false),
+ icon: 'pencil-alt',
+ });
+ ContextMenu.Instance.addItem({
+ description: 'Filter field',
+ event: () => this.openFilterMenu(index),
+ icon: 'filter',
+ });
+ ContextMenu.Instance.addItem({
+ description: 'Delete column',
+ event: () => this.removeColumn(index),
+ icon: 'trash',
+ });
+ ContextMenu.Instance.displayMenu(x, y, undefined, false);
+ };
- @computed
- get dividerDragger() {
- return this.previewWidth() === 0 ? null : (
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown}>
- <div className="collectionSchemaView-dividerDragger" />
- </div>
- );
- }
+ @action
+ updateKeySearch = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._menuValue = e.target.value;
+ this._menuOptions = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
+ };
+
+ getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
+
+ removeFieldFilters = (field: string) => {
+ this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove'));
+ };
- @computed
- get previewPanel() {
+ onFilterKeyDown = (e: React.KeyboardEvent) => {
+ //prettier-ignore
+ switch (e.key) {
+ case 'Enter' : this.closeFilterMenu(true); break;
+ case 'Escape': this.closeFilterMenu(false);break;
+ }
+ };
+
+ @action
+ updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterValue = e.target.value);
+
+ @computed get newFieldMenu() {
return (
- <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
- {!this.previewDocument ? null : (
- <DocumentView
- Document={this.previewDocument}
- DataDoc={undefined}
- fitContentsToBox={returnTrue}
- dontCenter={'y'}
- focus={DocUtils.DefaultFocus}
- renderDepth={this.props.renderDepth}
- rootSelected={this.rootSelected}
- PanelWidth={this.previewWidth}
- PanelHeight={this.previewHeight}
- isContentActive={returnTrue}
- isDocumentActive={returnFalse}
- ScreenToLocalTransform={this.getPreviewTransform}
- docFilters={this.childDocFilters}
- docRangeFilters={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- styleProvider={DefaultStyleProvider}
- docViewPath={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- moveDocument={this.props.moveDocument}
- addDocument={this.props.addDocument}
- removeDocument={this.props.removeDocument}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}
+ <div className="schema-new-key-options">
+ <div className="schema-key-type-option">
+ <input
+ type="radio"
+ name="newFieldType"
+ checked={this._newFieldType == ColumnType.Number}
+ onChange={action(() => {
+ this._newFieldType = ColumnType.Number;
+ this._newFieldDefault = 0;
+ })}
/>
- )}
+ number
+ </div>
+ <div className="schema-key-type-option">
+ <input
+ type="radio"
+ name="newFieldType"
+ checked={this._newFieldType == ColumnType.Boolean}
+ onChange={action(() => {
+ this._newFieldType = ColumnType.Boolean;
+ this._newFieldDefault = false;
+ })}
+ />
+ boolean
+ </div>
+ <div className="schema-key-type-option">
+ <input
+ type="radio"
+ name="newFieldType"
+ checked={this._newFieldType == ColumnType.String}
+ onChange={action(() => {
+ this._newFieldType = ColumnType.String;
+ this._newFieldDefault = '';
+ })}
+ />
+ string
+ </div>
+ <div className="schema-key-default-val">value: {this.fieldDefaultInput}</div>
+ <div className="schema-key-warning">{this._newFieldWarning}</div>
+ <div
+ className="schema-column-menu-button"
+ onPointerDown={action(e => {
+ if (this.documentKeys.includes(this._menuValue)) {
+ this._newFieldWarning = 'Field already exists';
+ } else if (this._menuValue.length === 0) {
+ this._newFieldWarning = 'Field cannot be an empty string';
+ } else {
+ this.setKey(this._menuValue, this._newFieldDefault);
+ }
+ })}>
+ done
+ </div>
</div>
);
}
- @computed
- get schemaTable() {
+ @computed get keysDropdown() {
return (
- <SchemaTable
- Document={this.props.Document}
- PanelHeight={this.props.PanelHeight}
- PanelWidth={this.props.PanelWidth}
- childDocs={this.childDocs}
- CollectionView={this.props.CollectionView}
- ContainingCollectionView={this.props.ContainingCollectionView}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- fieldKey={this.props.fieldKey}
- renderDepth={this.props.renderDepth}
- moveDocument={this.props.moveDocument}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- active={this.props.isContentActive}
- onDrop={this.onExternalDrop}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- isSelected={this.props.isSelected}
- isFocused={this.isFocused}
- setFocused={this.setFocused}
- setPreviewDoc={this.setPreviewDoc}
- deleteDocument={this.props.removeDocument}
- addDocument={this.props.addDocument}
- dataDoc={this.props.DataDoc}
- columns={this.columns}
- documentKeys={this.documentKeys}
- headerIsEditing={this._headerIsEditing}
- openHeader={this.openHeader}
- onClick={this.onTableClick}
- onPointerDown={emptyFunction}
- onResizedChange={this.onResizedChange}
- setColumns={this.setColumns}
- reorderColumns={this.reorderColumns}
- changeColumns={this.changeColumns}
- setHeaderIsEditing={this.setHeaderIsEditing}
- changeColumnSort={this.setColumnSort}
- />
+ <div className="schema-key-search">
+ <div
+ className="schema-column-menu-button"
+ onPointerDown={action(e => {
+ e.stopPropagation();
+ this._makeNewField = true;
+ })}>
+ + new field
+ </div>
+ <div
+ className="schema-key-list"
+ ref={r =>
+ r?.addEventListener(
+ 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
+ (e: WheelEvent) => {
+ if (!r.scrollTop && e.deltaY <= 0) e.preventDefault();
+ e.stopPropagation();
+ },
+ { passive: false }
+ )
+ }>
+ {this._menuOptions.map(key => (
+ <div
+ className="schema-key-search-result"
+ onPointerDown={e => {
+ e.stopPropagation();
+ this.setKey(key);
+ }}>
+ {key}
+ </div>
+ ))}
+ </div>
+ </div>
);
}
- @computed
- public get schemaToolbar() {
+ @computed get renderColumnMenu() {
+ const x = this._columnMenuIndex! == -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
return (
- <div className="collectionSchemaView-toolbar">
- <div className="collectionSchemaView-toolbar-item">
- <div id="preview-schema-checkbox-div">
- <input type="checkbox" key={'Show Preview'} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} />
- Show Preview
- </div>
+ <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()} />
+ {this._makeNewField ? this.newFieldMenu : this.keysDropdown}
+ <div
+ className="schema-column-menu-button"
+ onPointerDown={action(e => {
+ e.stopPropagation();
+ this.closeColumnMenu();
+ })}>
+ cancel
</div>
</div>
);
}
- onSpecificMenu = (e: React.MouseEvent) => {
- if ((e.target as any)?.className?.includes?.('collectionSchemaView-cell') || e.target instanceof HTMLSpanElement) {
- const cm = ContextMenu.Instance;
- const options = cm.findByDescription('Options...');
- const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
- optionItems.push({ description: 'remove', event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: 'trash' });
- !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
- cm.displayMenu(e.clientX, e.clientY);
- (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this.
- e.stopPropagation();
- }
- };
+ @computed get renderFilterOptions() {
+ const keyOptions: string[] = [];
+ const columnKey = this.columnKeys[this._filterColumnIndex!];
+ this.childDocs.forEach(doc => {
+ const key = StrCast(doc[columnKey]);
+ if (keyOptions.includes(key) === false && (key.includes(this._filterValue) || !this._filterValue) && key !== '') {
+ keyOptions.push(key);
+ }
+ });
- @action
- onTableClick = (e: React.MouseEvent): void => {
- if (!(e.target as any)?.className?.includes?.('collectionSchemaView-cell') && !(e.target instanceof HTMLSpanElement)) {
- this.setPreviewDoc(undefined);
- } else {
- e.stopPropagation();
+ const filters = StrListCast(this.Document._docFilters);
+ for (let i = 0; i < (filters?.length ?? 0) - 1; i++) {
+ if (filters[i] === columnKey && keyOptions.includes(filters[i].split(':')[1]) === false) {
+ keyOptions.push(filters[i + 1]);
+ }
}
- this.setFocused(this.props.Document);
- this.closeHeader();
- };
- onResizedChange = (newResized: Resize[], event: any) => {
- const columns = this.columns;
- newResized.forEach(resized => {
- const index = columns.findIndex(c => c.heading === resized.id);
- const column = columns[index];
- column.setWidth(resized.value);
- columns[index] = column;
+ const options = keyOptions.map(key => {
+ let bool = false;
+ if (filters !== undefined) {
+ const ind = filters.findIndex(filter => filter.split(':')[1] === key);
+ const fields = ind === -1 ? undefined : filters[ind].split(':');
+ bool = fields ? fields[2] === 'check' : false;
+ }
+ return (
+ <div key={key} className="schema-filter-option">
+ <input
+ type="checkbox"
+ onPointerDown={e => e.stopPropagation()}
+ onClick={e => e.stopPropagation()}
+ onChange={action(e => {
+ if (e.target.checked) {
+ Doc.setDocFilter(this.props.Document, columnKey, key, 'check');
+ } else {
+ Doc.setDocFilter(this.props.Document, columnKey, key, 'remove');
+ }
+ })}
+ checked={bool}
+ />
+ <span style={{ paddingLeft: 4 }}>{key}</span>
+ </div>
+ );
});
- this.columns = columns;
- };
- @action
- setColumns = (columns: SchemaHeaderField[]) => (this.columns = columns);
-
- @undoBatch
- reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => {
- const columns = [...columnsValues];
- const oldIndex = columns.indexOf(toMove);
- const relIndex = columns.indexOf(relativeTo);
- const newIndex = oldIndex > relIndex && !before ? relIndex + 1 : oldIndex < relIndex && before ? relIndex - 1 : relIndex;
-
- if (oldIndex === newIndex) return;
-
- columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]);
- this.columns = columns;
- };
-
- onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation();
+ return options;
+ }
- render() {
- TraceMobx();
- if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0);
- const menuContent = this.renderMenuContent;
- const menu = (
- <div className="collectionSchema-header-menu" onWheel={e => this.onZoomMenu(e)} onPointerDown={e => this.onHeaderClick(e)} style={{ transform: `translate(${this.menuCoordinates[0]}px, ${this.menuCoordinates[1]}px)` }}>
- <Measure
- offset
- onResize={action((r: any) => {
- const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height);
- this._menuWidth = dim[0];
- this._menuHeight = dim[1];
+ @computed get renderFilterMenu() {
+ const x = this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._filterColumnIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
+ return (
+ <div className="schema-filter-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}>
+ <input className="schema-filter-input" type="text" value={this._filterValue} onKeyDown={this.onFilterKeyDown} onChange={this.updateFilterSearch} onPointerDown={e => e.stopPropagation()} />
+ {this.renderFilterOptions}
+ <div
+ className="schema-column-menu-button"
+ onPointerDown={action(e => {
+ e.stopPropagation();
+ this.closeFilterMenu(true);
})}>
- {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>}
- </Measure>
+ done
+ </div>
</div>
);
+ }
+
+ @computed get sortedDocs() {
+ const field = StrCast(this.layoutDoc.sortField);
+ const desc = BoolCast(this.layoutDoc.sortDesc);
+ const docs = !field
+ ? this.childDocs
+ : this.childDocs.sort((docA, docB) => {
+ const aStr = Field.toString(docA[field] as Field);
+ const bStr = Field.toString(docB[field] as Field);
+ var out = 0;
+ if (aStr < bStr) out = -1;
+ if (aStr > bStr) out = 1;
+ if (desc) out *= -1;
+ return out;
+ });
+ return { docs };
+ }
+ sortedDocsFunc = () => this.sortedDocs;
+ isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ screenToLocal = () => this.props.ScreenToLocalTransform().translate(-this.tableWidth, 0);
+ previewWidthFunc = () => this.previewWidth;
+ render() {
return (
- <div
- className={'collectionSchemaView' + (this.props.Document._searchDoc ? '-searchContainer' : '-container')}
- style={{
- overflow: this.props.scrollOverflow === true ? 'scroll' : undefined,
- backgroundColor: 'white',
- pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined,
- width: this.props.PanelWidth() || '100%',
- height: this.props.PanelHeight() || '100%',
- position: 'relative',
- }}>
+ <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}>
<div
- className="collectionSchemaView-tableContainer"
- style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
- onContextMenu={this.onSpecificMenu}
- onPointerDown={this.onPointerDown}
- onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}
- onDrop={e => this.onExternalDrop(e, {})}
- ref={this.createTarget}>
- {this.schemaTable}
+ className="schema-table"
+ 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 });
+ }}>
+ <div className="schema-header-row" style={{ height: CollectionSchemaView._rowHeight }}>
+ <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>
+ </div>
+ {this.columnKeys.map((key, index) => (
+ <SchemaColumnHeader
+ key={index}
+ columnIndex={index}
+ columnKeys={this.columnKeys}
+ columnWidths={this.displayColumnWidths}
+ sortField={this.sortField}
+ sortDesc={this.sortDesc}
+ setSort={this.setSort}
+ removeColumn={this.removeColumn}
+ resizeColumn={this.startResize}
+ openContextMenu={this.openContextMenu}
+ dragColumn={this.dragColumn}
+ setColRef={this.setColRef}
+ />
+ ))}
+ </div>
+ {this._columnMenuIndex !== undefined && this.renderColumnMenu}
+ {this._filterColumnIndex !== undefined && this.renderFilterMenu}
+ <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} />
+
+ <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
</div>
- {this.dividerDragger}
- {!this.previewWidth() ? null : this.previewPanel}
- {this._headerOpen && this.props.isContentActive() ? menu : null}
+ {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
+ {this.previewWidth > 0 && (
+ <div style={{ width: `${this.previewWidth}px` }} ref={ref => (this._previewRef = ref)}>
+ {Array.from(this._selectedDocs).lastElement() && (
+ <DocumentView
+ Document={Array.from(this._selectedDocs).lastElement()}
+ DataDoc={undefined}
+ fitContentsToBox={returnTrue}
+ dontCenter={'y'}
+ onClickScriptDisable="always"
+ focus={emptyFunction}
+ renderDepth={this.props.renderDepth + 1}
+ rootSelected={this.rootSelected}
+ PanelWidth={this.previewWidthFunc}
+ PanelHeight={this.props.PanelHeight}
+ isContentActive={returnTrue}
+ isDocumentActive={returnFalse}
+ ScreenToLocalTransform={this.screenToLocal}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ styleProvider={DefaultStyleProvider}
+ docViewPath={returnEmptyDoclist}
+ moveDocument={this.props.moveDocument}
+ addDocument={this.addRow}
+ removeDocument={this.props.removeDocument}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ />
+ )}
+ </div>
+ )}
+ </div>
+ );
+ }
+}
+
+interface CollectionSchemaViewDocsProps {
+ schema: CollectionSchemaView;
+ childDocs: () => { docs: Doc[] };
+}
+
+@observer
+class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsProps> {
+ tableWidthFunc = () => this.props.schema.tableWidth;
+ rowHeightFunc = () => CollectionSchemaView._rowHeight;
+ childScreenToLocal = computedFn((index: number) => () => this.props.schema.props.ScreenToLocalTransform().translate(0, -CollectionSchemaView._rowHeight - index * this.rowHeightFunc()));
+ render() {
+ return (
+ <div className="schema-table-content">
+ {this.props.childDocs().docs.map((doc: Doc, index: number) => {
+ const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.schema.props.DataDoc;
+ return (
+ <div className="schema-row-wrapper" style={{ maxHeight: CollectionSchemaView._rowHeight }}>
+ <DocumentView
+ key={doc[Id]}
+ {...this.props.schema.props}
+ LayoutTemplate={this.props.schema.props.childLayoutTemplate}
+ LayoutTemplateString={SchemaRowBox.LayoutString(this.props.schema.props.fieldKey)}
+ Document={doc}
+ DataDoc={dataDoc}
+ renderDepth={this.props.schema.props.renderDepth + 1}
+ PanelWidth={this.tableWidthFunc}
+ PanelHeight={this.rowHeightFunc}
+ styleProvider={DefaultStyleProvider}
+ waitForDoubleClickToClick={returnNever}
+ defaultDoubleClick={returnDefault}
+ enableDragWhenActive={true}
+ onClickScriptDisable="always"
+ focus={this.props.schema.focusDocument}
+ docFilters={this.props.schema.childDocFilters}
+ docRangeFilters={this.props.schema.childDocRangeFilters}
+ searchFilterDocs={this.props.schema.searchFilterDocs}
+ rootSelected={this.props.schema.rootSelected}
+ ScreenToLocalTransform={this.childScreenToLocal(index)}
+ bringToFront={emptyFunction}
+ isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive}
+ isContentActive={emptyFunction}
+ whenChildContentsActiveChanged={active => this.props.schema.props.whenChildContentsActiveChanged(active)}
+ hideDecorations={true}
+ hideTitle={true}
+ hideDocumentButtonBar={true}
+ hideLinkAnchors={true}
+ fitWidth={returnTrue}
+ scriptContext={this}
+ />
+ </div>
+ );
+ })}
</div>
);
}
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
new file mode 100644
index 000000000..d88d67c94
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -0,0 +1,62 @@
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { Colors } from '../../global/globalEnums';
+import './CollectionSchemaView.scss';
+
+export interface SchemaColumnHeaderProps {
+ columnKeys: string[];
+ columnWidths: number[];
+ columnIndex: number;
+ sortField: string;
+ sortDesc: boolean;
+ setSort: (field: string, desc: boolean) => void;
+ removeColumn: (index: number) => void;
+ resizeColumn: (e: any, index: number) => void;
+ dragColumn: (e: any, index: number) => boolean;
+ openContextMenu: (x: number, y: number, index: number) => void;
+ setColRef: (index: number, ref: HTMLDivElement) => void;
+}
+
+@observer
+export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> {
+ @computed get fieldKey() {
+ return this.props.columnKeys[this.props.columnIndex];
+ }
+
+ @action
+ sortClicked = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ if (this.props.sortField == this.fieldKey) {
+ this.props.setSort(this.fieldKey, !this.props.sortDesc);
+ } else {
+ this.props.setSort(this.fieldKey, false);
+ }
+ };
+
+ @action
+ onPointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction, false);
+ };
+
+ render() {
+ return (
+ <div className="schema-column-header" style={{ width: this.props.columnWidths[this.props.columnIndex] }} onPointerDown={this.onPointerDown} ref={col => col && this.props.setColRef(this.props.columnIndex, col)}>
+ <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)}></div>
+ <div className="schema-column-title">{this.fieldKey}</div>
+
+ <div className="schema-header-menu">
+ <div className="schema-header-button" onPointerDown={e => this.props.openContextMenu(e.clientX, e.clientY, this.props.columnIndex)}>
+ <FontAwesomeIcon icon="ellipsis-h" />
+ </div>
+ <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField == this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}>
+ <FontAwesomeIcon icon="caret-right" style={this.props.sortField == this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} />
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
new file mode 100644
index 000000000..856537927
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -0,0 +1,136 @@
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import { DragManager } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { undoBatch } from '../../../util/UndoManager';
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { Colors } from '../../global/globalEnums';
+import { OpenWhere } from '../../nodes/DocumentView';
+import { FieldView, FieldViewProps } from '../../nodes/FieldView';
+import { CollectionSchemaView } from './CollectionSchemaView';
+import './CollectionSchemaView.scss';
+import { SchemaTableCell } from './SchemaTableCell';
+
+@observer
+export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(SchemaRowBox, fieldKey);
+ }
+
+ private _ref: HTMLDivElement | null = null;
+
+ bounds = () => this._ref?.getBoundingClientRect();
+
+ @computed get schemaView() {
+ const vpath = this.props.docViewPath();
+ return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as CollectionSchemaView) : undefined;
+ }
+
+ @computed get schemaDoc() {
+ return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
+ }
+
+ @computed get rowIndex() {
+ return this.schemaView?.rowIndex(this.rootDoc) ?? -1;
+ }
+
+ componentDidMount(): void {
+ this.props.setContentView?.(this);
+ }
+
+ select = (ctrlKey: boolean, shiftKey: boolean) => {
+ if (!this.schemaView) return;
+ const lastSelected = Array.from(this.schemaView._selectedDocs).lastElement();
+ if (shiftKey && lastSelected) this.schemaView.selectRows(this.rootDoc, lastSelected);
+ else {
+ this.props.select?.(ctrlKey);
+ }
+ };
+
+ onPointerEnter = (e: any) => {
+ if (!SnappingManager.GetIsDragging()) return;
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.addEventListener('pointermove', this.onPointerMove);
+ };
+
+ onPointerMove = (e: any) => {
+ if (!SnappingManager.GetIsDragging()) return;
+ const dragIsRow = DragManager.docsBeingDragged.some(doc => doc.context === this.schemaDoc); // this.schemaView?._selectedDocs.has(doc) ?? false;
+
+ if (this._ref && dragIsRow) {
+ const rect = this._ref.getBoundingClientRect();
+ const y = e.clientY - rect.top; //y position within the element.
+ const height = this._ref.clientHeight;
+ const halfLine = height / 2;
+ if (y <= halfLine) {
+ this._ref.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
+ this._ref.style.borderBottom = '0px';
+ this.schemaView?.setDropIndex(this.rowIndex);
+ } else if (y > halfLine) {
+ this._ref.style.borderTop = '0px';
+ this._ref.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
+ this.schemaView?.setDropIndex(this.rowIndex + 1);
+ }
+ }
+ };
+
+ onPointerLeave = (e: any) => {
+ if (this._ref) {
+ this._ref.style.borderTop = '0px';
+ this._ref.style.borderBottom = '0px';
+ }
+ document.removeEventListener('pointermove', this.onPointerMove);
+ };
+
+ render() {
+ return (
+ <div
+ className="schema-row"
+ style={{ height: CollectionSchemaView._rowHeight, backgroundColor: this.props.isSelected() ? Colors.LIGHT_BLUE : undefined, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined }}
+ onPointerEnter={this.onPointerEnter}
+ onPointerLeave={this.onPointerLeave}
+ ref={(row: HTMLDivElement | null) => {
+ row && this.schemaView?.addRowRef?.(this.rootDoc, row);
+ this._ref = row;
+ }}>
+ <div
+ className="row-menu"
+ style={{
+ width: CollectionSchemaView._rowMenuWidth,
+ pointerEvents: !this.props.isContentActive() ? 'none' : undefined,
+ }}>
+ <div
+ className="schema-row-button"
+ onPointerDown={undoBatch(e => {
+ e.stopPropagation();
+ this.props.removeDocument?.(this.rootDoc);
+ })}>
+ <FontAwesomeIcon icon="times" />
+ </div>
+ <div
+ className="schema-row-button"
+ onPointerDown={e => {
+ e.stopPropagation();
+ this.props.addDocTab(this.rootDoc, OpenWhere.addRight);
+ }}>
+ <FontAwesomeIcon icon="external-link-alt" />
+ </div>
+ </div>
+ <div className="row-cells">
+ {this.schemaView?.columnKeys?.map((key, index) => (
+ <SchemaTableCell
+ key={key}
+ Document={this.rootDoc}
+ fieldKey={key}
+ columnWidth={this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth}
+ isRowActive={this.props.isContentActive}
+ setColumnValues={(field, value) => this.schemaView?.setColumnValues(field, value) ?? false}
+ />
+ ))}
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx
deleted file mode 100644
index 16910cc83..000000000
--- a/src/client/views/collections/collectionSchema/SchemaTable.tsx
+++ /dev/null
@@ -1,694 +0,0 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from 'react-table';
-import { DateField } from '../../../../fields/DateField';
-import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { Id } from '../../../../fields/FieldSymbols';
-import { List } from '../../../../fields/List';
-import { listSpec } from '../../../../fields/Schema';
-import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { ComputedField } from '../../../../fields/ScriptField';
-import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
-import { ImageField } from '../../../../fields/URLField';
-import { GetEffectiveAcl } from '../../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../../Utils';
-import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents';
-import { DocumentType } from '../../../documents/DocumentTypes';
-import { CompileScript, Transformer, ts } from '../../../util/Scripting';
-import { Transform } from '../../../util/Transform';
-import { undoBatch } from '../../../util/UndoManager';
-import '../../../views/DocumentDecorations.scss';
-import { ContextMenu } from '../../ContextMenu';
-import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
-import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
-import { PinProps } from '../../nodes/trails';
-import { DefaultStyleProvider } from '../../StyleProvider';
-import { CollectionView } from '../CollectionView';
-import {
- CellProps,
- CollectionSchemaButtons,
- CollectionSchemaCell,
- CollectionSchemaCheckboxCell,
- CollectionSchemaDateCell,
- CollectionSchemaDocCell,
- CollectionSchemaImageCell,
- CollectionSchemaListCell,
- CollectionSchemaNumberCell,
- CollectionSchemaStringCell,
-} from './CollectionSchemaCells';
-import { CollectionSchemaAddColumnHeader, KeysDropdown } from './CollectionSchemaHeaders';
-import { MovableColumn } from './CollectionSchemaMovableColumn';
-import { MovableRow } from './CollectionSchemaMovableRow';
-import './CollectionSchemaView.scss';
-
-enum ColumnType {
- Any,
- Number,
- String,
- Boolean,
- Doc,
- Image,
- List,
- Date,
-}
-
-// this map should be used for keys that should have a const type of value
-const columnTypes: Map<string, ColumnType> = new Map([
- ['title', ColumnType.String],
- ['x', ColumnType.Number],
- ['y', ColumnType.Number],
- ['_width', ColumnType.Number],
- ['_height', ColumnType.Number],
- ['_nativeWidth', ColumnType.Number],
- ['_nativeHeight', ColumnType.Number],
- ['isPrototype', ColumnType.Boolean],
- ['_curPage', ColumnType.Number],
- ['_currentTimecode', ColumnType.Number],
- ['zIndex', ColumnType.Number],
-]);
-
-export interface SchemaTableProps {
- Document: Doc; // child doc
- dataDoc?: Doc;
- PanelHeight: () => number;
- PanelWidth: () => number;
- childDocs?: Doc[];
- CollectionView: Opt<CollectionView>;
- ContainingCollectionView: Opt<CollectionView>;
- ContainingCollectionDoc: Opt<Doc>;
- fieldKey: string;
- renderDepth: number;
- deleteDocument?: (document: Doc | Doc[]) => boolean;
- addDocument?: (document: Doc | Doc[]) => boolean;
- moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
- ScreenToLocalTransform: () => Transform;
- active: (outsideReaction: boolean | undefined) => boolean | undefined;
- onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
- addDocTab: (document: Doc, where: OpenWhere) => boolean;
- pinToPres: (document: Doc, pinProps: PinProps) => void;
- isSelected: (outsideReaction?: boolean) => boolean;
- isFocused: (document: Doc, outsideReaction: boolean) => boolean;
- setFocused: (document: Doc) => void;
- setPreviewDoc: (document: Opt<Doc>) => void;
- columns: SchemaHeaderField[];
- documentKeys: any[];
- headerIsEditing: boolean;
- openHeader: (column: any, screenx: number, screeny: number) => void;
- onClick: (e: React.MouseEvent) => void;
- onPointerDown: (e: React.PointerEvent) => void;
- onResizedChange: (newResized: Resize[], event: any) => void;
- setColumns: (columns: SchemaHeaderField[]) => void;
- reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => void;
- changeColumns: (oldKey: string, newKey: string, addNew: boolean) => void;
- setHeaderIsEditing: (isEditing: boolean) => void;
- changeColumnSort: (columnField: SchemaHeaderField, descending: boolean | undefined) => void;
-}
-
-@observer
-export class SchemaTable extends React.Component<SchemaTableProps> {
- @observable _cellIsEditing: boolean = false;
- @observable _focusedCell: { row: number; col: number } = { row: 0, col: 0 };
- @observable _openCollections: Set<number> = new Set();
-
- @observable _showDoc: Doc | undefined;
- @observable _showDataDoc: any = '';
- @observable _showDocPos: number[] = [];
-
- @observable _showTitleDropdown: boolean = false;
-
- @computed get previewWidth() {
- return () => NumCast(this.props.Document.schemaPreviewWidth);
- }
- @computed get previewHeight() {
- return () => this.props.PanelHeight() - 2 * this.borderWidth;
- }
- @computed get tableWidth() {
- return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth();
- }
-
- @computed get childDocs() {
- if (this.props.childDocs) return this.props.childDocs;
-
- const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- return DocListCast(doc[this.props.fieldKey]);
- }
- set childDocs(docs: Doc[]) {
- const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- doc[this.props.fieldKey] = new List<Doc>(docs);
- }
-
- @computed get textWrappedRows() {
- return Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []);
- }
- set textWrappedRows(textWrappedRows: string[]) {
- this.props.Document.textwrappedSchemaRows = new List<string>(textWrappedRows);
- }
-
- @computed get resized(): { id: string; value: number }[] {
- return this.props.columns.reduce((resized, shf) => {
- shf.width > -1 && resized.push({ id: shf.heading, value: shf.width });
- return resized;
- }, [] as { id: string; value: number }[]);
- }
- @computed get sorted(): SortingRule[] {
- return this.props.columns.reduce((sorted, shf) => {
- shf.desc !== undefined && sorted.push({ id: shf.heading, desc: shf.desc });
- return sorted;
- }, [] as SortingRule[]);
- }
-
- @action
- changeSorting = (col: any) => {
- this.props.changeColumnSort(col, col.desc === true ? false : col.desc === false ? undefined : true);
- };
-
- @action
- changeTitleMode = () => (this._showTitleDropdown = !this._showTitleDropdown);
-
- @computed get borderWidth() {
- return Number(COLLECTION_BORDER_WIDTH);
- }
- @computed get tableColumns(): Column<Doc>[] {
- const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1);
- const columns: Column<Doc>[] = [];
- const tableIsFocused = this.props.isFocused(this.props.Document, false);
- const focusedRow = this._focusedCell.row;
- const focusedCol = this._focusedCell.col;
- const isEditable = !this.props.headerIsEditing;
-
- columns.push({
- expander: true,
- Header: '',
- width: 58,
- Expander: rowInfo => {
- return rowInfo.original.type !== DocumentType.COL ? null : (
- <div className="collectionSchemaView-expander" onClick={action(() => this._openCollections[rowInfo.isExpanded ? 'delete' : 'add'](rowInfo.viewIndex))}>
- <FontAwesomeIcon icon={rowInfo.isExpanded ? 'caret-down' : 'caret-right'} size="lg" />
- </div>
- );
- },
- });
- columns.push(
- ...this.props.columns.map(col => {
- const icon: IconProp =
- this.getColumnType(col) === ColumnType.Number
- ? 'hashtag'
- : this.getColumnType(col) === ColumnType.String
- ? 'font'
- : this.getColumnType(col) === ColumnType.Boolean
- ? 'check-square'
- : this.getColumnType(col) === ColumnType.Doc
- ? 'file'
- : this.getColumnType(col) === ColumnType.Image
- ? 'image'
- : this.getColumnType(col) === ColumnType.List
- ? 'list-ul'
- : this.getColumnType(col) === ColumnType.Date
- ? 'calendar'
- : 'align-justify';
-
- const keysDropdown = (
- <KeysDropdown
- keyValue={col.heading}
- possibleKeys={possibleKeys}
- existingKeys={this.props.columns.map(c => c.heading)}
- canAddNew={true}
- addNew={false}
- onSelect={this.props.changeColumns}
- setIsEditing={this.props.setHeaderIsEditing}
- docs={this.props.childDocs}
- Document={this.props.Document}
- dataDoc={this.props.dataDoc}
- fieldKey={this.props.fieldKey}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- ContainingCollectionView={this.props.ContainingCollectionView}
- active={this.props.active}
- openHeader={this.props.openHeader}
- icon={icon}
- col={col}
- // try commenting this out
- width={'100%'}
- />
- );
-
- const sortIcon = col.desc === undefined ? 'caret-right' : col.desc === true ? 'caret-down' : 'caret-up';
- const header = (
- <div className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: '2px', display: 'flex', cursor: 'default', height: '100%' }}>
- {keysDropdown}
- <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: 'inline', zIndex: 1, background: 'inherit', cursor: 'pointer' }}>
- <FontAwesomeIcon icon={sortIcon} size="lg" />
- </div>
- {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>} */}
- </div>
- );
-
- return {
- Header: <MovableColumn columnRenderer={header} columnValue={col} allColumns={this.props.columns} reorderColumns={this.props.reorderColumns} ScreenToLocalTransform={this.props.ScreenToLocalTransform} />,
- accessor: (doc: Doc) => (doc ? Field.toString(doc[col.heading] as Field) : 0),
- id: col.heading,
- Cell: (rowProps: CellInfo) => {
- const rowIndex = rowProps.index;
- const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
- const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
-
- const props: CellProps = {
- row: rowIndex,
- col: columnIndex,
- rowProps: rowProps,
- isFocused: isFocused,
- changeFocusedCellByIndex: this.changeFocusedCellByIndex,
- CollectionView: this.props.CollectionView,
- ContainingCollection: this.props.ContainingCollectionView,
- Document: this.props.Document,
- fieldKey: this.props.fieldKey,
- renderDepth: this.props.renderDepth,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
- moveDocument: this.props.moveDocument,
- setIsEditing: this.setCellIsEditing,
- isEditable: isEditable,
- setPreviewDoc: this.props.setPreviewDoc,
- setComputed: this.setComputed,
- getField: this.getField,
- showDoc: this.showDoc,
- };
-
- switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) {
- case ColumnType.Number:
- return <CollectionSchemaNumberCell {...props} />;
- case ColumnType.String:
- return <CollectionSchemaStringCell {...props} />;
- case ColumnType.Boolean:
- return <CollectionSchemaCheckboxCell {...props} />;
- case ColumnType.Doc:
- return <CollectionSchemaDocCell {...props} />;
- case ColumnType.Image:
- return <CollectionSchemaImageCell {...props} />;
- case ColumnType.List:
- return <CollectionSchemaListCell {...props} />;
- case ColumnType.Date:
- return <CollectionSchemaDateCell {...props} />;
- default:
- return <CollectionSchemaCell {...props} />;
- }
- },
- minWidth: 200,
- };
- })
- );
- columns.push({
- Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />,
- accessor: (doc: Doc) => 0,
- id: 'add',
- Cell: (rowProps: CellInfo) => {
- const rowIndex = rowProps.index;
- const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
- const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
- return (
- <CollectionSchemaButtons
- {...{
- row: rowProps.index,
- col: columnIndex,
- rowProps: rowProps,
- isFocused: isFocused,
- changeFocusedCellByIndex: this.changeFocusedCellByIndex,
- CollectionView: this.props.CollectionView,
- ContainingCollection: this.props.ContainingCollectionView,
- Document: this.props.Document,
- fieldKey: this.props.fieldKey,
- renderDepth: this.props.renderDepth,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
- moveDocument: this.props.moveDocument,
- setIsEditing: this.setCellIsEditing,
- isEditable: isEditable,
- setPreviewDoc: this.props.setPreviewDoc,
- setComputed: this.setComputed,
- getField: this.getField,
- showDoc: this.showDoc,
- }}
- />
- );
- },
- width: 28,
- resizable: false,
- });
- return columns;
- }
-
- constructor(props: SchemaTableProps) {
- super(props);
- if (this.props.Document._schemaHeaders === undefined) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>([
- new SchemaHeaderField('title', '#f1efeb'),
- new SchemaHeaderField('author', '#f1efeb'),
- new SchemaHeaderField('*lastModified', '#f1efeb', ColumnType.Date),
- new SchemaHeaderField('text', '#f1efeb', ColumnType.String),
- new SchemaHeaderField('type', '#f1efeb'),
- new SchemaHeaderField('context', '#f1efeb', ColumnType.Doc),
- ]);
- }
- }
-
- componentDidMount() {
- document.addEventListener('keydown', this.onKeyDown);
- }
-
- componentWillUnmount() {
- document.removeEventListener('keydown', this.onKeyDown);
- }
-
- tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
- const tableDoc = this.props.Document[DataSym];
- const effectiveAcl = GetEffectiveAcl(tableDoc);
-
- if (effectiveAcl !== AclPrivate && effectiveAcl !== AclReadonly) {
- doc.context = this.props.Document;
- tableDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now()));
- return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
- }
- return false;
- };
-
- private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
- return !rowInfo
- ? {}
- : {
- ScreenToLocalTransform: this.props.ScreenToLocalTransform,
- addDoc: this.tableAddDoc,
- removeDoc: this.props.deleteDocument,
- rowInfo,
- rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true),
- textWrapRow: this.toggleTextWrapRow,
- rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1,
- dropAction: StrCast(this.props.Document.childDropAction),
- addDocTab: this.props.addDocTab,
- };
- };
-
- private getTdProps: ComponentPropsGetterR = (state, rowInfo, column, instance) => {
- if (!rowInfo || column) return {};
-
- const row = rowInfo.index;
- //@ts-ignore
- const col = this.columns.map(c => c.heading).indexOf(column!.id);
- const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true);
- // TODO: editing border doesn't work :(
- return {
- style: { border: !this.props.headerIsEditing && isFocused ? '2px solid rgb(255, 160, 160)' : '1px solid #f1efeb' },
- };
- };
-
- @action setCellIsEditing = (isEditing: boolean) => (this._cellIsEditing = isEditing);
-
- @action
- onKeyDown = (e: KeyboardEvent): void => {
- if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {
- // && this.props.isSelected(true)) {
- const direction = e.key === 'Tab' ? 'tab' : e.which === 39 ? 'right' : e.which === 37 ? 'left' : e.which === 38 ? 'up' : e.which === 40 ? 'down' : '';
- this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col);
-
- if (direction) {
- const pdoc = FieldValue(this.childDocs[this._focusedCell.row]);
- pdoc && this.props.setPreviewDoc(pdoc);
- e.stopPropagation();
- }
- } else if (e.keyCode === 27) {
- this.props.setPreviewDoc(undefined);
- e.stopPropagation(); // stopPropagation for left/right arrows
- }
- };
-
- changeFocusedCellByDirection = (direction: string, curRow: number, curCol: number) => {
- switch (direction) {
- case 'tab':
- return { row: curRow + 1 === this.childDocs.length ? 0 : curRow + 1, col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 };
- case 'right':
- return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 };
- case 'left':
- return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 };
- case 'up':
- return { row: curRow === 0 ? curRow : curRow - 1, col: curCol };
- case 'down':
- return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol };
- }
- return this._focusedCell;
- };
-
- @action
- changeFocusedCellByIndex = (row: number, col: number): void => {
- if (this._focusedCell.row !== row || this._focusedCell.col !== col) {
- this._focusedCell = { row: row, col: col };
- }
- this.props.setFocused(this.props.Document);
- };
-
- @undoBatch
- createRow = action(() => {
- this.props.addDocument?.(Docs.Create.TextDocument('', { title: '', _width: 100, _height: 30 }));
- this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col };
- });
-
- @undoBatch
- @action
- createColumn = () => {
- const newFieldName = (index: number) => `New field${index ? ` (${index})` : ''}`;
- for (let index = 0; index < 100; index++) {
- if (this.props.columns.findIndex(col => col.heading === newFieldName(index)) === -1) {
- this.props.columns.push(new SchemaHeaderField(newFieldName(index), '#f1efeb'));
- break;
- }
- }
- };
-
- @action
- getColumnType = (column: SchemaHeaderField, doc?: Doc, field?: string): ColumnType => {
- if (doc && field && column.type === ColumnType.Any) {
- const val = doc[CollectionSchemaCell.resolvedFieldKey(field, doc)];
- if (val instanceof ImageField) return ColumnType.Image;
- if (val instanceof Doc) return ColumnType.Doc;
- if (val instanceof DateField) return ColumnType.Date;
- if (val instanceof List) return ColumnType.List;
- }
- if (column.type && column.type !== 0) {
- return column.type;
- }
- if (columnTypes.get(column.heading)) {
- return (column.type = columnTypes.get(column.heading)!);
- }
- return (column.type = ColumnType.Any);
- };
-
- @undoBatch
- @action
- toggleTextwrap = async () => {
- const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []);
- if (textwrappedRows.length) {
- this.props.Document.textwrappedSchemaRows = new List<string>([]);
- } else {
- const docs = DocListCast(this.props.Document[this.props.fieldKey]);
- const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
- this.props.Document.textwrappedSchemaRows = new List<string>(allRows);
- }
- };
-
- @action
- toggleTextWrapRow = (doc: Doc): void => {
- const textWrapped = this.textWrappedRows;
- const index = textWrapped.findIndex(id => doc[Id] === id);
-
- index > -1 ? textWrapped.splice(index, 1) : textWrapped.push(doc[Id]);
-
- this.textWrappedRows = textWrapped;
- };
-
- @computed
- get reactTable() {
- const children = this.childDocs;
- const hasCollectionChild = children.reduce((found, doc) => found || doc.type === DocumentType.COL, false);
- const expanded: { [name: string]: any } = {};
- Array.from(this._openCollections.keys()).map(col => (expanded[col.toString()] = true));
- const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :((((
-
- return (
- <ReactTable
- style={{ position: 'relative' }}
- data={children}
- page={0}
- pageSize={children.length}
- showPagination={false}
- columns={this.tableColumns}
- getTrProps={this.getTrProps}
- getTdProps={this.getTdProps}
- sortable={false}
- TrComponent={MovableRow}
- sorted={this.sorted}
- expanded={expanded}
- resized={this.resized}
- onResizedChange={this.props.onResizedChange}
- // if it has a child, render another table with the children
- SubComponent={
- !hasCollectionChild
- ? undefined
- : row =>
- row.original.type !== DocumentType.COL ? null : (
- <div style={{ paddingLeft: 57 + 'px' }} className="reactTable-sub">
- <SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} />
- </div>
- )
- }
- />
- );
- }
-
- onContextMenu = (e: React.MouseEvent): void => {
- ContextMenu.Instance.addItem({ description: 'Toggle text wrapping', event: this.toggleTextwrap, icon: 'table' });
- };
-
- getField = (row: number, col?: number) => {
- const docs = this.childDocs;
-
- row = row % docs.length;
- while (row < 0) row += docs.length;
- const columns = this.props.columns;
- const doc = docs[row];
- if (col === undefined) {
- return doc;
- }
- if (col >= 0 && col < columns.length) {
- const column = this.props.columns[col].heading;
- return doc[column];
- }
- return undefined;
- };
-
- createTransformer = (row: number, col: number): Transformer => {
- const self = this;
- const captures: { [name: string]: Field } = {};
-
- const transformer: ts.TransformerFactory<ts.SourceFile> = context => {
- return root => {
- function visit(node: ts.Node) {
- node = ts.visitEachChild(node, visit, context);
- if (ts.isIdentifier(node)) {
- const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
- const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
- if (isntPropAccess && isntPropAssign) {
- if (node.text === '$r') {
- return ts.createNumericLiteral(row.toString());
- } else if (node.text === '$c') {
- return ts.createNumericLiteral(col.toString());
- } else if (node.text === '$') {
- if (ts.isCallExpression(node.parent)) {
- // captures.doc = self.props.Document;
- // captures.key = self.props.fieldKey;
- }
- }
- }
- }
-
- return node;
- }
- return ts.visitNode(root, visit);
- };
- };
-
- // const getVars = () => {
- // return { capturedVariables: captures };
- // };
-
- return { transformer /*getVars*/ };
- };
-
- setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => {
- script = `const $ = (row:number, col?:number) => {
- const rval = (doc as any)[key][row + ${row}];
- return col === undefined ? rval : rval[(doc as any)._schemaHeaders[col + ${col}].heading];
- }
- return ${script}`;
- const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: false, transformer: this.createTransformer(row, col) });
- if (compiled.compiled) {
- doc[field] = new ComputedField(compiled);
- return true;
- }
- return false;
- };
-
- @action
- showDoc = (doc: Doc | undefined, dataDoc?: Doc, screenX?: number, screenY?: number) => {
- this._showDoc = doc;
- if (dataDoc && screenX && screenY) {
- this._showDocPos = this.props.ScreenToLocalTransform().transformPoint(screenX, screenY);
- }
- };
-
- onOpenClick = () => {
- this._showDoc && this.props.addDocTab(this._showDoc, OpenWhere.addRight);
- };
-
- getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(-this.borderWidth - 4 - this.tableWidth, -this.borderWidth);
- };
-
- render() {
- const preview = '';
- return (
- <div
- className="collectionSchemaView-table"
- onPointerDown={this.props.onPointerDown}
- onClick={this.props.onClick}
- onWheel={e => this.props.active(true) && e.stopPropagation()}
- onDrop={e => this.props.onDrop(e, {})}
- onContextMenu={this.onContextMenu}>
- {this.reactTable}
- {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : (
- <div className="collectionSchemaView-addRow" onClick={this.createRow}>
- + new
- </div>
- )}
- {!this._showDoc ? null : (
- <div
- className="collectionSchemaView-documentPreview"
- ref="overlay"
- style={{
- position: 'absolute',
- width: 150,
- height: 150,
- background: 'dimgray',
- display: 'block',
- top: 0,
- left: 0,
- transform: `translate(${this._showDocPos[0]}px, ${this._showDocPos[1] - 180}px)`,
- }}>
- <DocumentView
- Document={this._showDoc}
- DataDoc={this._showDataDoc}
- styleProvider={DefaultStyleProvider}
- docViewPath={returnEmptyDoclist}
- focus={DocUtils.DefaultFocus}
- renderDepth={this.props.renderDepth}
- rootSelected={returnFalse}
- isContentActive={returnTrue}
- isDocumentActive={returnFalse}
- PanelWidth={() => 150}
- PanelHeight={() => 150}
- ScreenToLocalTransform={this.getPreviewTransform}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- moveDocument={this.props.moveDocument}
- whenChildContentsActiveChanged={emptyFunction}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}></DocumentView>
- </div>
- )}
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
new file mode 100644
index 000000000..5f8ffe8b0
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -0,0 +1,67 @@
+import React = require('react');
+import { observer } from 'mobx-react';
+import { Doc, Field } from '../../../../fields/Doc';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
+import { Transform } from '../../../util/Transform';
+import { EditableView } from '../../EditableView';
+import { FieldView, FieldViewProps } from '../../nodes/FieldView';
+import { KeyValueBox } from '../../nodes/KeyValueBox';
+import { DefaultStyleProvider } from '../../StyleProvider';
+import { CollectionSchemaView } from './CollectionSchemaView';
+import './CollectionSchemaView.scss';
+
+export interface SchemaTableCellProps {
+ Document: Doc;
+ fieldKey: string;
+ columnWidth: number;
+ isRowActive: () => boolean | undefined;
+ setColumnValues: (field: string, value: string) => boolean;
+}
+
+@observer
+export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
+ render() {
+ const props: FieldViewProps = {
+ Document: this.props.Document,
+ docFilters: returnEmptyFilter,
+ docRangeFilters: returnEmptyFilter,
+ searchFilterDocs: returnEmptyDoclist,
+ styleProvider: DefaultStyleProvider,
+ docViewPath: returnEmptyDoclist,
+ fieldKey: this.props.fieldKey,
+ rootSelected: returnFalse,
+ isSelected: returnFalse,
+ setHeight: returnFalse,
+ select: emptyFunction,
+ dropAction: 'alias',
+ bringToFront: emptyFunction,
+ renderDepth: 1,
+ isContentActive: returnFalse,
+ whenChildContentsActiveChanged: emptyFunction,
+ ScreenToLocalTransform: Transform.Identity,
+ focus: emptyFunction,
+ PanelWidth: () => this.props.columnWidth,
+ PanelHeight: () => CollectionSchemaView._rowHeight,
+ addDocTab: returnFalse,
+ pinToPres: returnZero,
+ };
+
+ return (
+ <div className="schema-table-cell" style={{ width: this.props.columnWidth }}>
+ <div className="schemacell-edit-wrapper" style={this.props.isRowActive() ? { cursor: 'text', pointerEvents: 'auto' } : { cursor: 'default', pointerEvents: 'none' }}>
+ <EditableView
+ contents={<FieldView {...props} />}
+ GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+ SetValue={(value: string, shiftDown?: boolean, enterKey?: boolean) => {
+ if (shiftDown && enterKey) {
+ this.props.setColumnValues(this.props.fieldKey, value);
+ }
+ return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value);
+ }}
+ editing={this.props.isRowActive() ? undefined : false}
+ />
+ </div>
+ </div>
+ );
+ }
+}