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.scss14
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx174
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx30
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx131
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx44
-rw-r--r--src/client/views/collections/CollectionMenu.tsx129
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.scss20
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx141
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx87
-rw-r--r--src/client/views/collections/CollectionPileView.tsx45
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss2
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx156
-rw-r--r--src/client/views/collections/CollectionStackingView.scss6
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx171
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx34
-rw-r--r--src/client/views/collections/CollectionSubView.tsx102
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx75
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx73
-rw-r--r--src/client/views/collections/CollectionView.tsx122
-rw-r--r--src/client/views/collections/TabDocView.scss6
-rw-r--r--src/client/views/collections/TabDocView.tsx140
-rw-r--r--src/client/views/collections/TreeView.tsx276
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx48
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx65
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx1083
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx97
-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.tsx67
-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.scss716
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx1352
-rw-r--r--src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx78
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx152
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTable.tsx694
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx313
43 files changed, 3228 insertions, 4938 deletions
diff --git a/src/client/views/collections/CollectionCarousel3DView.scss b/src/client/views/collections/CollectionCarousel3DView.scss
index 5c8b491eb..6bd1d9f5f 100644
--- a/src/client/views/collections/CollectionCarousel3DView.scss
+++ b/src/client/views/collections/CollectionCarousel3DView.scss
@@ -1,3 +1,4 @@
+@import '../global/globalCssVariables';
.collectionCarousel3DView-outer {
height: 100%;
position: relative;
@@ -7,8 +8,8 @@
.carousel-wrapper {
display: flex;
position: absolute;
- top: 15%;
- height: 60%;
+ top: $CAROUSEL3D_TOP * 1%;
+ height: ($CAROUSEL3D_SIDE_SCALE * 100) * 1%;
align-items: center;
transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
@@ -17,10 +18,17 @@
flex: 1;
transition: opacity 0.3s linear, transform 0.5s cubic-bezier(0.455, 0.03, 0.515, 0.955);
pointer-events: none;
+ opacity: 0.5;
+ z-index: 1;
+ transform: scale($CAROUSEL3D_SIDE_SCALE);
+ user-select: none;
}
.collectionCarousel3DView-item-active {
pointer-events: unset;
+ opacity: 1;
+ z-index: 2;
+ transform: scale($CAROUSEL3D_CENTER_SCALE);
}
}
@@ -105,4 +113,4 @@
.carousel3DView-back-scroll:hover,
.carousel3DView-fwd-scroll:hover {
background: lightgray;
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 01f41869e..d94e552b4 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -5,11 +5,13 @@ 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 { SelectionManager } from '../../util/SelectionManager';
+import { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } from '../global/globalCssVariables.scss';
import { DocumentView } from '../nodes/DocumentView';
import { StyleProp } from '../StyleProvider';
-import "./CollectionCarousel3DView.scss";
+import './CollectionCarousel3DView.scss';
import { CollectionSubView } from './CollectionSubView';
@observer
@@ -20,134 +22,152 @@ 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);
}
- }
+ };
+ centerScale = Number(CAROUSEL3D_CENTER_SCALE);
panelWidth = () => this.props.PanelWidth() / 3;
- panelHeight = () => this.props.PanelHeight() * 0.6;
+ panelHeight = () => this.props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE);
onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
+ isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
+ isChildContentActive = () => (this.isContentActive() ? true : false);
+ childScreenToLocal = () =>
+ this.props // document's left is the panel shifted by the doc's index * panelWidth/#docs. But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1)
+ .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor.
+ .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this.props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
+ .scale(1 / this.centerScale);
+
@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 currentIndex = NumCast(this.layoutDoc._carousel_index);
+ 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}
+ ScreenToLocalTransform={this.childScreenToLocal}
+ 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={{ width: this.panelWidth() }}>
{displayDoc(childPair)}
- </div>);
- }));
+ </div>
+ );
+ });
}
changeSlide = (direction: number) => {
- this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length;
- }
+ SelectionManager.DeselectAll();
+ this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 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._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />);
+ }
+ @computed get translateX() {
+ const index = NumCast(this.layoutDoc._carousel_index);
+ return this.panelWidth() * (1 - 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(${this.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..0eb61a0b2 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 { returnFalse, returnZero, StopEvent } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -30,37 +30,38 @@ export class CollectionCarouselView extends CollectionSubView() {
advance = (e: React.MouseEvent) => {
e.stopPropagation();
- this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) + 1) % this.childLayoutPairs.length;
+ this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length;
};
goback = (e: React.MouseEvent) => {
e.stopPropagation();
- this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
+ this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
};
captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<DocumentViewProps>, property: string): any => {
// first look for properties on the document in the carousel, then fallback to properties on the container
const childValue = doc?.['caption-' + property] ? this.props.styleProvider?.(doc, captionProps, property) : undefined;
return childValue ?? this.props.styleProvider?.(this.layoutDoc, captionProps, property);
};
- panelHeight = () => this.props.PanelHeight() - (StrCast(this.layoutDoc._showCaption) ? 50 : 0);
+ panelHeight = () => this.props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0);
onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
onContentClick = () => ScriptCast(this.layoutDoc.onChildClick);
@computed get content() {
- const index = NumCast(this.layoutDoc._itemIndex);
+ const index = NumCast(this.layoutDoc._carousel_index);
const curDoc = this.childLayoutPairs?.[index];
- const captionProps = { ...OmitKeys(this.props, ['setHeight']).omit, fieldKey: 'caption' };
- const marginX = NumCast(this.layoutDoc['caption-xMargin']);
- const marginY = NumCast(this.layoutDoc['caption-yMargin']);
- const showCaptions = StrCast(this.layoutDoc._showCaption);
+ const captionProps = { ...this.props, fieldKey: 'caption', setHeight: undefined };
+ const marginX = NumCast(this.layoutDoc['caption_xMargin']);
+ const marginY = NumCast(this.layoutDoc['caption_yMargin']);
+ const show_captions = StrCast(this.layoutDoc._layout_showCaption);
return !(curDoc?.layout instanceof Doc) ? null : (
<>
<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}
+ hideCaptions={show_captions ? true : false}
renderDepth={this.props.renderDepth + 1}
- ContainingCollectionView={this.props.CollectionView}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
Document={curDoc.layout}
@@ -72,14 +73,15 @@ export class CollectionCarouselView extends CollectionSubView() {
<div
className="collectionCarouselView-caption"
key="caption"
+ onWheel={StopEvent}
style={{
- display: showCaptions ? undefined : 'none',
+ display: show_captions ? undefined : 'none',
borderRadius: this.props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding),
marginRight: marginX,
marginLeft: marginX,
width: `calc(100% - ${marginX * 2}px)`,
}}>
- <FormattedTextBox key={index} {...captionProps} fieldKey={showCaptions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} DataDoc={undefined} />
+ <FormattedTextBox key={index} {...captionProps} allowScroll={true} fieldKey={show_captions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} DataDoc={undefined} />
</div>
</>
);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1ead80bd0..e9cc2c894 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -6,7 +6,7 @@ import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
-import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { inheritParentAcls } from '../../../fields/util';
import { emptyFunction, incrementTitleCopy } from '../../../Utils';
@@ -20,18 +20,21 @@ import { SelectionManager } from '../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { DashboardView } from '../DashboardView';
import { LightboxView } from '../LightboxView';
+import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { OverlayView } from '../OverlayView';
+import { ScriptingRepl } from '../ScriptingRepl';
+import { UndoStack } from '../UndoStack';
import './CollectionDockingView.scss';
import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
import { TabDocView } from './TabDocView';
import React = require('react');
-import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
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 +42,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
},
};
@@ -82,6 +86,7 @@ export class CollectionDockingView extends CollectionSubView() {
tabItemDropped = () => DragManager.CompleteWindowDrag?.(false);
tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => {
+ this._flush = this._flush ?? UndoManager.StartBatch('tab move');
const dashDoc = proxy?._contentItem?.tab?.DashDoc as Doc;
dashDoc && (DragManager.DocDragData = new DragManager.DocumentDragData([proxy._contentItem.tab.DashDoc]));
DragManager.CompleteWindowDrag = (aborted: boolean) => {
@@ -89,12 +94,12 @@ export class CollectionDockingView extends CollectionSubView() {
proxy._dragListener.AbortDrag();
if (this._flush) {
this._flush.cancel(); // cancel the undo change being logged
- this._flush = undefined;
this.setupGoldenLayout(); // restore golden layout to where it was before the drag (this is a no-op when using StartOtherDrag because the proxy dragged item was never in the golden layout)
}
DragManager.CompleteWindowDrag = undefined;
}
finishDrag?.(aborted);
+ setTimeout(this.endUndoBatch, 100);
};
};
@undoBatch
@@ -124,12 +129,12 @@ export class CollectionDockingView extends CollectionSubView() {
SelectionManager.DeselectAll();
const instance = CollectionDockingView.Instance;
if (instance) {
- if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === 'layout') {
+ if (doc._type_collection === CollectionViewType.Docking && doc.layout_fieldKey === 'layout') {
return DashboardView.openDashboard(doc);
}
const newItemStackConfig = {
type: 'stack',
- content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))],
+ content: [CollectionDockingView.makeDocumentConfig(Doc.MakeEmbedding(doc))],
};
const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
instance._goldenLayout.root.contentItems[0].addChild(docconfig);
@@ -143,10 +148,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,21 +173,20 @@ 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);
}
//
// Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side
//
- @undoBatch
@action
- public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string) {
- if (document?._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document);
+ public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
+ if (document?._type_collection === 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,8 +194,10 @@ 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);
+ CollectionDockingView.Instance._flush = CollectionDockingView.Instance._flush ?? UndoManager.StartBatch('Add Split');
+ setTimeout(CollectionDockingView.Instance.endUndoBatch, 100);
if (!pullSide && stack) {
stack.addChild(docContentConfig, undefined);
setTimeout(() => stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1]));
@@ -367,16 +373,30 @@ export class CollectionDockingView extends CollectionSubView() {
!LightboxView.LightboxDoc && cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height);
};
+ endUndoBatch = () => {
+ const json = JSON.stringify(this._goldenLayout.toConfig());
+ const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+ const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', ''));
+ const docs = !docids
+ ? []
+ : docids
+ .map(id => DocServer.GetCachedRefField(id))
+ .filter(f => f)
+ .map(f => f as Doc);
+ const changesMade = this.props.Document.dockingConfig !== json;
+ if (changesMade) {
+ this.props.Document.dockingConfig = json;
+ this.props.Document.data = new List<Doc>(docs);
+ }
+ this._flush?.end();
+ this._flush = undefined;
+ };
+
@action
onPointerUp = (e: MouseEvent): void => {
window.removeEventListener('pointerup', this.onPointerUp);
- const flush = this._flush;
- this._flush = undefined;
- if (flush) {
- DragManager.CompleteWindowDrag = undefined;
- if (!this.stateChanged()) flush.cancel();
- else flush.end();
- }
+ DragManager.CompleteWindowDrag = undefined;
+ setTimeout(this.endUndoBatch, 100);
};
@action
@@ -390,10 +410,8 @@ export class CollectionDockingView extends CollectionSubView() {
window.addEventListener('mouseup', this.onPointerUp);
if (!htmlTarget.closest('*.lm_content') && (htmlTarget.closest('*.lm_tab') || htmlTarget.closest('*.lm_stack'))) {
const className = typeof htmlTarget.className === 'string' ? htmlTarget.className : '';
- if (!className.includes('lm_close') && !className.includes('lm_maximise')) {
- this._flush = UndoManager.StartBatch('golden layout edit');
- DocServer.UPDATE_SERVER_CACHE();
- }
+ if (className.includes('lm_maximise')) this._flush = UndoManager.StartBatch('tab maximize');
+ else if (!className.includes('lm_close')) DocServer.UPDATE_SERVER_CACHE();
}
}
if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
@@ -407,11 +425,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_nativeWidth'] = _width;
+ proto['thumb_nativeHeight'] = _height;
+ proto.thumb = new ImageField(iconFile);
});
}
}
@@ -432,11 +449,11 @@ export class CollectionDockingView extends CollectionSubView() {
.map(f => f as Doc);
const newtabs = origtabs.map(origtab => {
const origtabdocs = DocListCast(origtab.data);
- const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true, undefined, true) : Doc.MakeAlias(origtab);
- const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeAlias(origtabdoc));
+ const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true, undefined, true) : Doc.MakeEmbedding(origtab);
+ const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeEmbedding(origtabdoc));
if (newtabdocs.length) {
Doc.GetProto(newtab).data = new List<Doc>(newtabdocs);
- newtabdocs.forEach(ntab => (ntab.context = newtab));
+ newtabdocs.forEach(ntab => (ntab.embedContainer = newtab));
}
json = json.replace(origtab[Id], newtab[Id]);
return newtab;
@@ -450,25 +467,12 @@ export class CollectionDockingView extends CollectionSubView() {
stateChanged = () => {
this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
const json = JSON.stringify(this._goldenLayout.toConfig());
- const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
- const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', ''));
- const docs = !docids
- ? []
- : docids
- .map(id => DocServer.GetCachedRefField(id))
- .filter(f => f)
- .map(f => f as Doc);
const changesMade = this.props.Document.dockingConfig !== json;
- if (changesMade && !this._flush) {
- UndoManager.RunInBatch(() => {
- this.props.Document.dockingConfig = json;
- this.props.Document.data = new List<Doc>(docs);
- }, 'state changed');
- }
return changesMade;
};
tabDestroyed = (tab: any) => {
+ this._flush = this._flush ?? UndoManager.StartBatch('tab movement');
if (tab.DashDoc && ![DocumentType.KVP, DocumentType.PRES].includes(tab.DashDoc?.type)) {
Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc);
Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
@@ -479,7 +483,6 @@ export class CollectionDockingView extends CollectionSubView() {
Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
this.tabMap.delete(tab);
tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
- //tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
this.stateChanged();
}
};
@@ -497,8 +500,8 @@ export class CollectionDockingView extends CollectionSubView() {
const docToAdd = Docs.Create.FreeformDocument([], {
_width: this.props.PanelWidth(),
_height: this.props.PanelHeight(),
- _backgroundGridShow: true,
- _fitWidth: true,
+ _freeform_backgroundGrid: true,
+ _layout_fitWidth: true,
title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
});
this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
@@ -540,8 +543,8 @@ export class CollectionDockingView extends CollectionSubView() {
const docToAdd = Docs.Create.FreeformDocument([], {
_width: this.props.PanelWidth(),
_height: this.props.PanelHeight(),
- _fitWidth: true,
- _backgroundGridShow: true,
+ _layout_fitWidth: true,
+ _freeform_backgroundGrid: true,
title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
});
this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
@@ -580,19 +583,29 @@ 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:
+ // prettier-ignore
+ switch (doc) {
+ case '<ScriptingRepl />': return OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
+ case "<UndoStack>": return OverlayView.Instance.addWindow(<UndoStack />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
+ }
+ Doc.AddToMyOverlay(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/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index befd89e41..528781991 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -44,7 +44,7 @@ interface CMVFieldRowProps {
@observer
export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowProps> {
@observable private _background = 'inherit';
- @observable private _createAliasSelected: boolean = false;
+ @observable private _createEmbeddingSelected: boolean = false;
@observable private heading: string = '';
@observable private color: string = '#f1efeb';
@observable private collapsed: boolean = false;
@@ -95,7 +95,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@undoBatch
rowDrop = action((e: Event, de: DragManager.DropEvent) => {
- this._createAliasSelected = false;
+ this._createEmbeddingSelected = false;
if (de.complete.docDragData) {
this.props.parent.Document.dropConverter instanceof ScriptField && this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData });
const key = this.props.pivotField;
@@ -117,12 +117,12 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@action
headingChanged = (value: string, shiftDown?: boolean) => {
- this._createAliasSelected = false;
+ this._createEmbeddingSelected = false;
const key = this.props.pivotField;
const castedValue = this.getValue(value);
if (castedValue) {
- if (this.props.parent.columnHeaders) {
- if (this.props.parent.columnHeaders.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
+ if (this.props.parent.colHeaderData) {
+ if (this.props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
return false;
}
}
@@ -135,7 +135,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@action
changeColumnColor = (color: string) => {
- this._createAliasSelected = false;
+ this._createEmbeddingSelected = false;
this._color = color;
};
@@ -143,16 +143,16 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@action
pointerLeaveRow = () => {
- this._createAliasSelected = false;
+ this._createEmbeddingSelected = false;
this._background = 'inherit';
};
@action
addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
if (!value && !forceEmptyNote) return false;
- this._createAliasSelected = false;
+ this._createEmbeddingSelected = false;
const key = this.props.pivotField;
- const newDoc = Docs.Create.TextDocument('', { _autoHeight: true, _width: 200, _fitWidth: true, title: value });
+ const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
const onLayoutDoc = this.onLayoutDoc(key);
FormattedTextBox.SelectOnLoad = newDoc[Id];
FormattedTextBox.SelectOnLoadChar = value;
@@ -163,33 +163,33 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
deleteRow = undoBatch(
action(() => {
- this._createAliasSelected = false;
+ this._createEmbeddingSelected = false;
const key = this.props.pivotField;
this.props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
- if (this.props.parent.columnHeaders && this.props.headingObject) {
- const index = this.props.parent.columnHeaders.indexOf(this.props.headingObject);
- this.props.parent.columnHeaders.splice(index, 1);
+ if (this.props.parent.colHeaderData && this.props.headingObject) {
+ const index = this.props.parent.colHeaderData.indexOf(this.props.headingObject);
+ this.props.parent.colHeaderData.splice(index, 1);
}
})
);
@action
collapseSection = (e: any) => {
- this._createAliasSelected = false;
+ this._createEmbeddingSelected = false;
this.toggleVisibility();
e.stopPropagation();
};
headerMove = (e: PointerEvent) => {
- const alias = Doc.MakeAlias(this.props.Document);
+ const embedding = Doc.MakeEmbedding(this.props.Document);
const key = this.props.pivotField;
let value = this.getValue(this.heading);
value = typeof value === 'string' ? `"${value}"` : value;
const script = `return doc.${key} === ${value}`;
const compiled = CompileScript(script, { params: { doc: Doc.name } });
if (compiled.compiled) {
- alias.viewSpecScript = new ScriptField(compiled);
- DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY);
+ embedding.viewSpecScript = new ScriptField(compiled);
+ DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([embedding]), e.clientX, e.clientY);
}
return true;
};
@@ -198,7 +198,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
if (e.button === 0 && !e.ctrlKey) {
setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this.props.chromeHidden && this.collapseSection(e));
- this._createAliasSelected = false;
+ this._createEmbeddingSelected = false;
}
};
@@ -242,16 +242,16 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
);
};
- toggleAlias = action(() => (this._createAliasSelected = true));
+ toggleEmbedding = action(() => (this._createEmbeddingSelected = true));
toggleVisibility = () => (this._collapsed = !this.collapsed);
renderMenu = () => {
- const selected = this._createAliasSelected;
+ const selected = this._createEmbeddingSelected;
return (
<div className="collectionStackingView-optionPicker">
<div className="optionOptions">
- <div className={'optionPicker' + (selected === true ? ' active' : '')} onClick={this.toggleAlias}>
- Create Alias
+ <div className={'optionPicker' + (selected === true ? ' active' : '')} onClick={this.toggleEmbedding}>
+ Create Embedding
</div>
<div className={'optionPicker' + (selected === true ? ' active' : '')} onClick={this.deleteRow}>
Delete
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index e2594b6ae..6dd76465e 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -15,6 +15,7 @@ import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
@@ -40,7 +41,7 @@ import { CollectionLinearView } from './collectionLinear';
import './CollectionMenu.scss';
import { COLLECTION_BORDER_WIDTH } from './CollectionView';
import { TabDocView } from './TabDocView';
-import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { CollectionFreeFormView } from './collectionFreeForm';
interface CollectionMenuProps {
panelHeight: () => number;
@@ -107,8 +108,8 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
<CollectionLinearView
Document={selDoc}
DataDoc={undefined}
- fieldKey={'data'}
- dropAction={'alias'}
+ fieldKey="data"
+ dropAction="embed"
setHeight={returnFalse}
styleProvider={DefaultStyleProvider}
rootSelected={returnTrue}
@@ -119,7 +120,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
isSelected={returnFalse}
docViewPath={returnEmptyDoclist}
moveDocument={returnFalse}
- CollectionView={undefined}
addDocument={returnFalse}
addDocTab={returnFalse}
pinToPres={emptyFunction}
@@ -133,8 +133,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
/>
</div>
);
@@ -174,7 +172,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
// [<CollectionViewBaseChrome key="chrome"
// docView={this.SelectedCollection}
// fieldKey={this.SelectedCollection.LayoutFieldKey}
- // type={StrCast(this.SelectedCollection?.props.Document._viewType, CollectionViewType.Invalid) as CollectionViewType} />,
+ // type={StrCast(this.SelectedCollection?.props.Document._type_collection, CollectionViewType.Invalid) as CollectionViewType} />,
// prop,
// /*button*/]);
}
@@ -245,52 +243,35 @@ 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',
- script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale']; gotoFrame(self.target, self['target-currentFrame']);",
+ script: "self.target._freeform_panX = self['target-freeform_panX']; self.target._freeform_panY = self['target-freeform_panY']; self.target._freeform_scale = self['target_freeform_scale']; gotoFrame(self.target, self['target-currentFrame']);",
immediate: undoBatch((source: Doc[]) => {
- this.target._panX = 0;
- this.target._panY = 0;
- this.target._viewScale = 1;
+ this.target._freeform_panX = 0;
+ this.target._freeform_panY = 0;
+ this.target._freeform_scale = 1;
this.target._currentFrame = this.target._currentFrame === undefined ? undefined : 0;
}),
initialize: (button: Doc) => {
- button['target-panX'] = this.target._panX;
- button['target-panY'] = this.target._panY;
- button['target-viewScale'] = this.target._viewScale;
+ button['target-panX'] = this.target._freeform_panX;
+ button['target-panY'] = this.target._freeform_panY;
+ button['target-_ayout_scale'] = this.target._freeform_scale;
button['target-currentFrame'] = this.target._currentFrame;
},
};
_clusterCommand = {
params: ['target'],
title: 'fit content',
- script: 'self.target._fitContentsToBox = !self.target._fitContentsToBox;',
- immediate: undoBatch((source: Doc[]) => (this.target._fitContentsToBox = !this.target._fitContentsToBox)),
+ script: 'self.target._freeform_fitContentsToBox = !self.target._freeform_fitContentsToBox;',
+ immediate: undoBatch((source: Doc[]) => (this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox)),
initialize: emptyFunction,
};
_fitContentCommand = {
params: ['target'],
title: 'toggle clusters',
- script: 'self.target._useClusters = !self.target._useClusters;',
- immediate: undoBatch((source: Doc[]) => (this.target._useClusters = !this.target._useClusters)),
+ script: 'self.target._freeform_useClusters = !self.target._freeform_useClusters;',
+ immediate: undoBatch((source: Doc[]) => (this.target._freeform_useClusters = !this.target._freeform_useClusters)),
initialize: emptyFunction,
};
_saveFilterCommand = {
@@ -327,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;
@@ -368,7 +349,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
viewChanged = (e: React.ChangeEvent) => {
const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : (this.document.proto as Doc);
//@ts-ignore
- target._viewType = e.target.selectedOptions[0].value;
+ target._type_collection = e.target.selectedOptions[0].value;
};
commandChanged = (e: React.ChangeEvent) => {
@@ -459,8 +440,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
const c = {
params: ['target'],
title: vtype,
- script: `this.target._viewType = '${StrCast(this.props.type)}'`,
- immediate: (source: Doc[]) => (this.document._viewType = Doc.getDocTemplate(source?.[0])),
+ script: `this.target._type_collection = '${StrCast(this.props.type)}'`,
+ immediate: (source: Doc[]) => (this.document._type_collection = Doc.getDocTemplate(source?.[0])),
initialize: emptyFunction,
};
DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, e.clientX, e.clientY);
@@ -567,8 +548,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
@undoBatch
@action
startRecording = () => {
- const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _fitWidth: true, _width: 400, _height: 200, mediaState: 'pendingRecording' });
- //Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+ const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _layout_fitWidth: true, _width: 400, _height: 200, mediaState: 'pendingRecording' });
CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
};
@@ -587,29 +567,29 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
}
@undoBatch
- onAlias = () => {
+ onEmbed = () => {
if (this.selectedDoc && this.selectedDocumentView) {
// const copy = Doc.MakeCopy(this.selectedDocumentView.props.Document, true);
// copy.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width);
// copy.y = NumCast(this.selectedDoc.y) + 30;
// this.selectedDocumentView.props.addDocument?.(copy);
- const alias = Doc.MakeAlias(this.selectedDoc);
- alias.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width);
- alias.y = NumCast(this.selectedDoc.y) + 30;
- this.selectedDocumentView.props.addDocument?.(alias);
+ const embedding = Doc.MakeEmbedding(this.selectedDoc);
+ embedding.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width);
+ embedding.y = NumCast(this.selectedDoc.y) + 30;
+ this.selectedDocumentView.props.addDocument?.(embedding);
}
};
- onAliasButtonDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction);
+ onEmbedButtonDown = (e: React.PointerEvent): void => {
+ setupMoveUpEvents(this, e, this.onEmbedButtonMoved, emptyFunction, emptyFunction);
};
@undoBatch
- onAliasButtonMoved = (e: PointerEvent) => {
+ onEmbedButtonMoved = (e: PointerEvent) => {
const contentDiv = this.selectedDocumentView?.ContentDiv;
if (contentDiv && this.selectedDoc) {
const dragData = new DragManager.DocumentDragData([this.selectedDoc]);
const offset = [e.clientX - contentDiv.getBoundingClientRect().x, e.clientY - contentDiv.getBoundingClientRect().y];
- dragData.defaultDropAction = 'alias';
+ dragData.defaultDropAction = 'embed';
dragData.canEmbed = true;
DragManager.StartDocumentDrag([contentDiv], dragData, e.clientX, e.clientY, {
offsetX: offset[0],
@@ -622,11 +602,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
};
@computed
- get aliasButton() {
+ get embedButton() {
const targetDoc = this.selectedDoc;
return !targetDoc || targetDoc.type === DocumentType.PRES ? null : (
- <Tooltip title={<div className="dash-tooltip">{'Tap or Drag to create an alias'}</div>} placement="top">
- <button className="antimodeMenu-button" onPointerDown={this.onAliasButtonDown} onClick={this.onAlias} style={{ cursor: 'drag' }}>
+ <Tooltip title={<div className="dash-tooltip">{'Tap or Drag to create an embedding'}</div>} placement="top">
+ <button className="antimodeMenu-button" onPointerDown={this.onEmbedButtonDown} onClick={this.onEmbed} style={{ cursor: 'drag' }}>
<FontAwesomeIcon className="colMenu-icon" icon="copy" size="lg" />
</button>
</Tooltip>
@@ -658,8 +638,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?._type_collection !== CollectionViewType.Freeform ? 'none' : undefined,
+ color: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._type_collection !== CollectionViewType.Freeform ? 'dimgrey' : undefined,
}}
onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}>
<FontAwesomeIcon icon={['fab', 'buffer']} size={'lg'} />
@@ -676,7 +656,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
<div className="collectionViewBaseChrome">
{this.notACollection || this.props.type === CollectionViewType.Invalid ? null : this.viewModes}
<div className="collectionMenu-divider" key="divider1"></div>
- {this.aliasButton}
+ {this.embedButton}
{/* {this.pinButton} */}
{this.toggleOverlayButton}
<div className="collectionMenu-divider" key="divider2"></div>
@@ -710,7 +690,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
return this.props.docView.props.Document;
}
@computed get dataField() {
- return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? '-annotations' : '')];
+ return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? '_annotations' : '')];
}
@computed get childDocs() {
return DocListCast(this.dataField);
@@ -733,7 +713,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
doc._currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
}
- CollectionFreeFormDocumentView.updateKeyframe(undefined, childDocs, currentFrame || 0);
+ CollectionFreeFormView.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0);
doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
}
}
@@ -747,7 +727,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
this.document._currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
- this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0);
+ this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.document], currentFrame || 0);
this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame));
};
@@ -759,7 +739,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
this.document._currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
- this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice());
+ this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.document], 1000);
this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
};
@@ -817,7 +797,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
if (doc.type === DocumentType.INK) {
switch (field) {
case 'width':
- doc.strokeWidth = Number(value);
+ doc.stroke_width = Number(value);
break;
case 'color':
doc.color = String(value);
@@ -826,7 +806,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
doc.fillColor = String(value);
break;
case 'dash':
- doc.strokeDash = value;
+ doc.stroke_dash = value;
}
}
})
@@ -1024,16 +1004,13 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
if (Doc.noviceMode) {
if (docs instanceof Doc) {
- const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
+ const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
- } else {
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- const noviceKeys = Array.from(keys).filter(
- key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
- );
- return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
+ const keys = new Set<string>();
+ docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+ const noviceKeys = Array.from(keys).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_'));
+ return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
if (docs instanceof Doc) {
@@ -1144,13 +1121,13 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
if (Doc.UserDoc().noviceMode) {
if (docs instanceof Doc) {
- const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
+ const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
} else {
const keys = new Set<string>();
docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
const noviceKeys = Array.from(keys).filter(
- key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
+ key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
);
return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
@@ -1254,9 +1231,9 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewMe
const dividerWidth = 4;
const borderWidth = Number(COLLECTION_BORDER_WIDTH);
const panelWidth = this.props.docView.props.PanelWidth();
- const previewWidth = NumCast(this.document.schemaPreviewWidth);
+ const previewWidth = NumCast(this.document.schema_previewWidth);
const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
- this.document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
+ this.document.schema_previewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
};
@undoBatch
@@ -1273,7 +1250,7 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewMe
};
render() {
- const previewWidth = NumCast(this.document.schemaPreviewWidth);
+ const previewWidth = NumCast(this.document.schema_previewWidth);
const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec('string'), []).length > 0;
return (
@@ -1348,7 +1325,7 @@ export class Collection3DCarouselViewChrome extends React.Component<CollectionVi
return (
<div className="collection3DCarouselViewChrome-cont">
<div className="collection3DCarouselViewChrome-scrollSpeed-cont">
- {FormattedTextBox.Focused ? <RichTextMenu /> : null}
+ {/* {FormattedTextBox.Focused ? <RichTextMenu /> : null} */}
<div className="collectionStackingViewChrome-scrollSpeed-label">AUTOSCROLL SPEED:</div>
<div className="collection3DCarouselViewChrome-scrollSpeed">
<EditableView GetValue={() => StrCast(this.scrollSpeed)} oneLine SetValue={this.setValue} contents={this.scrollSpeed ? this.scrollSpeed : 1000} />
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..a65e23911 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';
@@ -50,20 +49,20 @@ export class CollectionNoteTakingView extends CollectionSubView() {
return BoolCast(this.layoutDoc.chromeHidden);
}
// columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns
- @computed get columnHeaders() {
- const columnHeaders = 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) {
+ @computed get colHeaderData() {
+ const colHeaderData = Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null);
+ const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !colHeaderData?.find(sh => sh.heading === 'unset'));
+ if (needsUnsetCategory || colHeaderData === undefined || colHeaderData.length === 0) {
setTimeout(() => {
- const columnHeaders = Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null);
+ const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_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);
}
});
}
- return columnHeaders ?? ([] as SchemaHeaderField[]);
+ return colHeaderData ?? ([] as SchemaHeaderField[]);
}
@computed get headerMargin() {
return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin);
@@ -79,7 +78,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
}
// numGroupColumns returns the number of columns
@computed get numGroupColumns() {
- return this.columnHeaders.length;
+ return this.colHeaderData.length;
}
// PanelWidth returns the size of the total available space the view occupies
@computed get PanelWidth() {
@@ -118,7 +117,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// (2) documentView gets unmounted as you remove it from the list
@computed get Sections() {
TraceMobx();
- const columnHeaders = this.columnHeaders;
+ const columnHeaders = this.colHeaderData;
// filter out the currently dragged docs from the child docs, since we will insert them later
const docs = this.childDocs.filter(d => !DragManager.docsBeingDragged.includes(d));
const sections = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
@@ -150,9 +149,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
componentDidMount() {
super.componentDidMount?.();
document.addEventListener('pointerup', this.removeDocDragHighlight, true);
- this._disposers.autoHeight = reaction(
- () => this.layoutDoc._autoHeight,
- autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', ''))))))
+ this._disposers.layout_autoHeight = reaction(
+ () => this.layoutDoc._layout_autoHeight,
+ layout_autoHeight =>
+ layout_autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', ''))))))
);
}
@@ -163,7 +163,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 +187,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 +213,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,13 +224,14 @@ 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}
PanelHeight={height}
styleProvider={this.styleProvider}
docViewPath={this.props.docViewPath}
- fitWidth={this.props.childFitWidth}
+ layout_fitWidth={this.props.childLayoutFitWidth}
isContentActive={emptyFunction}
onKey={this.onKeyDown}
//TODO: change this from a prop to a parameter passed into a function
@@ -241,12 +239,12 @@ export class CollectionNoteTakingView extends CollectionSubView() {
isDocumentActive={this.isContentActive}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
- NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
- NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeHeight(doc)) ? height : undefined}
+ NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (doc._layout_fitWidth && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
+ NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (doc._layout_fitWidth && !Doc.NativeHeight(doc)) ? height : undefined}
dontCenter={this.props.childIgnoreNativeSize ? 'xy' : undefined}
dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
rootSelected={this.rootSelected}
- showTitle={this.props.childShowTitle}
+ layout_showTitle={this.props.childlayout_showTitle}
dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
@@ -258,8 +256,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 +272,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);
}
@@ -285,10 +281,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// if a note doc. Otherwise, returns the normal width (for graphs, images, etc...)
getDocWidth(d: Doc) {
const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field);
- const existingHeader = this.columnHeaders.find(sh => sh.heading === heading);
+ const existingHeader = this.colHeaderData.find(sh => sh.heading === heading);
const existingWidth = existingHeader?.width ? existingHeader.width : 0;
const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth;
- const width = d.fitWidth ? maxWidth : d[WidthSym]();
+ const width = d.layout_fitWidth ? maxWidth : d[WidthSym]();
return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth);
}
@@ -296,16 +292,16 @@ 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);
+ const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[WidthSym]() : 0);
+ const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[HeightSym]() : 0);
if (nw && nh) {
const docWid = this.getDocWidth(d);
return Math.min(maxHeight, (docWid * nh) / nw);
}
const childHeight = NumCast(childLayoutDoc._height);
- const panelHeight = childLayoutDoc._fitWidth || this.props.childFitWidth?.(d) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
+ const panelHeight = childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
return Math.min(childHeight, maxHeight, panelHeight);
}
@@ -319,18 +315,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[this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>(
+ headers.map(h => {
+ h.setWidth(Math.abs(h.width) * scaleFactor);
+ return h;
+ })
+ );
return true;
};
@@ -359,7 +353,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
});
// we alter the pivot fields of the docs in case they are moved to a new column.
const colIndex = this.getColumnFromXCoord(xCoord);
- const colHeader = colIndex === undefined ? 'unset' : StrCast(this.columnHeaders[colIndex].heading);
+ const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading);
DragManager.docsBeingDragged.forEach(d => (d[this.notetakingCategoryField] = colHeader));
// used to notify sections to re-render
this.docsDraggedRowCol.length = 0;
@@ -372,12 +366,12 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// This function is used to know which document a column SHOULD be in while it is being dragged.
getColumnFromXCoord = (xCoord: number): number | undefined => {
let colIndex: number | undefined = undefined;
- const numColumns = this.columnHeaders.length;
+ const numColumns = this.colHeaderData.length;
const coords = [];
let colStartXCoord = 0;
for (let i = 0; i < numColumns; i++) {
coords.push(colStartXCoord);
- colStartXCoord += this.columnHeaders[i].width * this.availableWidth + this.DividerWidth;
+ colStartXCoord += this.colHeaderData[i].width * this.availableWidth + this.DividerWidth;
}
coords.push(this.PanelWidth);
for (let i = 0; i < numColumns; i++) {
@@ -393,7 +387,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
getDocsFromXCoord = (xCoord: number): Doc[] => {
const docsMatchingHeader: Doc[] = [];
const colIndex = this.getColumnFromXCoord(xCoord);
- const colHeader = colIndex === undefined ? 'unset' : StrCast(this.columnHeaders[colIndex].heading);
+ const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading);
this.childDocs?.map(d => {
if (d instanceof Promise) return;
const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset';
@@ -408,7 +402,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
@action
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
const docView = fieldProps.DocumentView?.();
- if (docView && (e.ctrlKey || docView.rootDoc._singleLine) && ['Enter'].includes(e.key)) {
+ if (docView && (e.ctrlKey || docView.rootDoc._createDocOnCR) && ['Enter'].includes(e.key)) {
e.stopPropagation?.();
const newDoc = Doc.MakeCopy(docView.rootDoc, true);
Doc.GetProto(newDoc).text = undefined;
@@ -443,10 +437,10 @@ 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' });
+ } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
+ const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_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(), { link_relationship: '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;
@@ -475,7 +469,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
this.onPointerMove(true, e.clientX, e.clientY);
docus?.map((doc: Doc) => this.addDocument(doc));
const newDoc = this.childDocs.lastElement();
- const colHeader = colInd === undefined ? 'unset' : StrCast(this.columnHeaders[colInd].heading);
+ const colHeader = colInd === undefined ? 'unset' : StrCast(this.colHeaderData[colInd].heading);
newDoc[this.notetakingCategoryField] = colHeader;
const docs = this.childDocList;
if (docs && targInd !== -1) {
@@ -509,7 +503,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
this.refList.push(ref);
this.observer = new _global.ResizeObserver(
action((entries: any) => {
- if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
+ if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
this.props.setHeight?.(height);
@@ -520,9 +514,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
this.observer.observe(ref);
}
}}
+ select={this.props.select}
addDocument={this.addDocument}
chromeHidden={this.chromeHidden}
- columnHeaders={this.columnHeaders}
+ colHeaderData={this.colHeaderData}
Document={this.props.Document}
DataDoc={this.props.DataDoc}
resizeColumns={this.resizeColumns}
@@ -530,10 +525,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
numGroupColumns={this.numGroupColumns}
gridGap={this.gridGap}
pivotField={this.notetakingCategoryField}
+ fieldKey={this.fieldKey}
dividerWidth={this.DividerWidth}
maxColWidth={this.maxColWidth}
availableWidth={this.availableWidth}
- PanelWidth={this.PanelWidth}
key={heading?.heading ?? 'unset'}
headings={this.headings}
heading={heading?.heading ?? 'unset'}
@@ -553,18 +548,18 @@ export class CollectionNoteTakingView extends CollectionSubView() {
@undoBatch
@action
addGroup = (value: string) => {
- if (this.columnHeaders) {
- for (const header of this.columnHeaders) {
- if (header.heading == value) {
+ if (this.colHeaderData) {
+ for (const header of this.colHeaderData) {
+ 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.dataDoc[this.fieldKey + '_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;
};
@@ -573,7 +568,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
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: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_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' });
}
@@ -583,8 +578,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
@action
setColumnStartXCoords = (movementXScreen: number, colIndex: number) => {
const movementX = this.props.ScreenToLocalTransform().transformDirection(movementXScreen, 0)[0];
- const leftHeader = this.columnHeaders[colIndex];
- const rightHeader = this.columnHeaders[colIndex + 1];
+ const leftHeader = this.colHeaderData[colIndex];
+ const rightHeader = this.colHeaderData[colIndex + 1];
leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth);
rightHeader.setWidth(rightHeader.width - movementX / this.availableWidth);
};
@@ -637,8 +632,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..2f28ecd00 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -29,14 +29,16 @@ interface CSVFieldColumnProps {
docList: Doc[];
heading: string;
pivotField: string;
+ fieldKey: string | undefined;
chromeHidden?: boolean;
- columnHeaders: SchemaHeaderField[] | undefined;
+ colHeaderData: SchemaHeaderField[] | undefined;
headingObject: SchemaHeaderField | undefined;
yMargin: number;
numGroupColumns: number;
gridGap: number;
type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
headings: () => object[];
+ select: (ctrlPressed: boolean) => void;
renderChildren: (docs: Doc[]) => JSX.Element[];
addDocument: (doc: Doc | Doc[]) => boolean;
createDropTarget: (ele: HTMLDivElement) => void;
@@ -44,8 +46,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;
@@ -61,9 +62,9 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
// columnWidth returns the width of a column in absolute pixels
@computed get columnWidth() {
- if (!this.props.columnHeaders || !this.props.headingObject || this.props.columnHeaders.length === 1) return '100%';
- const i = this.props.columnHeaders.indexOf(this.props.headingObject);
- return this.props.columnHeaders[i].width * 100 + '%';
+ if (!this.props.colHeaderData || !this.props.headingObject || this.props.colHeaderData.length === 1) return '100%';
+ const i = this.props.colHeaderData.indexOf(this.props.headingObject);
+ return this.props.colHeaderData[i].width * 100 + '%';
}
private dropDisposer?: DragManager.DragDropDisposer;
@@ -105,7 +106,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
headingChanged = (value: string, shiftDown?: boolean) => {
const castedValue = this.getValue(value);
if (castedValue) {
- if (this.props.columnHeaders?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
+ if (this.props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
return false;
}
this.props.docList.forEach(d => (d[this.props.pivotField] = castedValue));
@@ -120,14 +121,15 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
@action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4');
@action pointerLeave = () => (this._background = 'inherit');
- textCallback = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+ @undoBatch
+ addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
// addNewTextDoc is called when a user starts typing in a column to create a new node
@action
addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
if (!value && !forceEmptyNote) return false;
const key = this.props.pivotField;
- const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _fitWidth: true, title: value, _autoHeight: true });
+ const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
const colValue = this.getValue(this.props.heading);
newDoc[key] = colValue;
FormattedTextBox.SelectOnLoad = newDoc[Id];
@@ -141,18 +143,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 colHdrData = Array.from(Cast(this.props.Document[this.props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
+ if (this.props.headingObject) {
+ this.props.docList.forEach(d => (d[this.props.pivotField] = undefined));
+ colHdrData.splice(colHdrData.indexOf(this.props.headingObject), 1);
+ this.props.resizeColumns(colHdrData);
}
};
@@ -218,7 +213,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
!Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
Doc.GetProto(this.props.Document)[name] = '';
- const created = Docs.Create.TextDocument('', { title: name, _width: 250, _autoHeight: true });
+ const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true });
if (created) {
if (this.props.Document.isTemplateDoc) {
Doc.MakeMetadataFieldTemplate(created, this.props.Document);
@@ -248,9 +243,9 @@ 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 && (
+ {(this.props.colHeaderData?.length ?? 0) > 1 && (
<button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
<FontAwesomeIcon icon="trash" size="lg" />
</button>
@@ -262,28 +257,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} />
+ {!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.addTextNote} 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 key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton">
- <EditableView {...this.props.editableViewProps()} />
- </div>
- </div>
- ) : null}
+ ) : null}
+ </div>
</div>
</>
);
@@ -294,7 +291,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..ea0fbbc54 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,6 +1,6 @@
import { action, computed, IReactionDisposer, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
+import { Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc';
import { NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
@@ -26,12 +26,6 @@ export class CollectionPileView extends CollectionSubView() {
}
this._originalChrome = this.layoutDoc._chromeHidden;
this.layoutDoc._chromeHidden = true;
-
- // pileups are designed to go away when they are empty.
- this._disposers.selected = reaction(
- () => this.childDocs.length,
- num => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document)
- );
}
componentWillUnmount() {
this.layoutDoc._chromeHidden = this._originalChrome;
@@ -48,13 +42,15 @@ export class CollectionPileView extends CollectionSubView() {
@undoBatch
removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
- (doc instanceof Doc ? [doc] : doc).map(undoBatch(d => Doc.deiconifyView(d)));
- return this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
+ (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d));
+ const ret = this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
+ if (ret && !DocListCast(this.rootDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.rootDoc);
+ return ret;
};
- toggleIcon = () => {
+ @computed get toggleIcon() {
return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' });
- };
+ }
// returns the contents of the pileup in a CollectionFreeFormView
@computed get contents() {
@@ -63,11 +59,11 @@ export class CollectionPileView extends CollectionSubView() {
<div className="collectionPileView-innards" style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : 'none' }}>
<CollectionFreeFormView
{...this.props}
+ childContentsActive={returnFalse}
layoutEngine={this.layoutEngine}
- childDocumentsActive={isStarburst ? returnTrue : undefined}
addDocument={this.addPileDoc}
childCanEmbedOnDrag={true}
- childClickScript={this.toggleIcon()}
+ childClickScript={this.toggleIcon}
moveDocument={this.removePileDoc}
/>
</div>
@@ -77,26 +73,25 @@ export class CollectionPileView extends CollectionSubView() {
// toggles the pileup between starburst to compact
toggleStarburst = action(() => {
if (this.layoutEngine() === computeStarburstLayout.name) {
+ if (this.rootDoc[WidthSym]() !== NumCast(this.rootDoc._starburstDiameter, 500)) {
+ this.rootDoc._starburstDiameter = this.rootDoc[WidthSym]();
+ }
const defaultSize = 110;
this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
- this.layoutDoc._panX = 0;
- this.layoutDoc._panY = -10;
+ this.layoutDoc._freeform_panX = 0;
+ this.layoutDoc._freeform_panY = -10;
this.props.Document._pileLayoutEngine = computePassLayout.name;
} else {
- const defaultSize = 25;
- !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250);
- !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
- if (this.layoutEngine() === computePassLayout.name) {
- this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
- this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
- this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
- this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
- }
- this.layoutDoc._panX = this.layoutDoc._panY = 0;
+ const defaultSize = NumCast(this.rootDoc._starburstDiameter, 500);
+ this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
+ this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
+ this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
+ this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
+ this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
this.layoutDoc._width = this.layoutDoc._height = defaultSize;
this.props.Document._pileLayoutEngine = computeStarburstLayout.name;
}
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..b131d38d8 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,9 +23,8 @@ 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 { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../nodes/DocumentView';
import { LabelBox } from '../nodes/LabelBox';
import { VideoBox } from '../nodes/VideoBox';
import './CollectionStackedTimeline.scss';
@@ -41,7 +41,9 @@ export type CollectionStackedTimelineProps = {
mediaPath: string;
dictationKey: string;
rawDuration: number;
+ dataFieldKey: string;
fieldKey: string;
+ thumbnails?: () => string[];
};
// trimming state: shows full clip, current trim bounds, or not trimming
@@ -54,7 +56,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;
@@ -80,6 +82,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
get minTrimLength() {
return Math.max(this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0, 0.5);
}
+ @computed get thumbnails() {
+ return this.props.thumbnails?.();
+ }
@computed get trimStart() {
return this.IsTrimming !== TrimScope.None ? this._trimStart : this.clipStart;
@@ -102,7 +107,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}
@computed get currentTime() {
- return NumCast(this.layoutDoc._currentTimecode);
+ return NumCast(this.layoutDoc._layout_currentTimecode);
}
@computed get zoomFactor() {
@@ -114,7 +119,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
// onClick play scripts
CollectionStackedTimeline.RangeScript =
CollectionStackedTimeline.RangeScript ||
- ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, {
+ ScriptField.MakeFunction(`setTimeout(() => scriptContext.clickAnchor(this, clientX))`, {
+ // setTimeout is a hack to run script in its own properly named undo group (instead of being part of the generic onClick)
self: Doc.name,
scriptContext: 'any',
clientX: 'number',
@@ -179,18 +185,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;
}
@@ -216,12 +225,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
};
getLinkData(l: Doc) {
- let la1 = l.anchor1 as Doc;
- let la2 = l.anchor2 as Doc;
+ let la1 = l.link_anchor_1 as Doc;
+ let la2 = l.link_anchor_2 as Doc;
const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag]));
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
- la1 = l.anchor2 as Doc;
- la2 = l.anchor1 as Doc;
+ la1 = l.link_anchor_2 as Doc;
+ la2 = l.link_anchor_1 as Doc;
}
return { la1, la2, linkTime };
}
@@ -257,7 +266,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 +282,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));
}
@@ -289,10 +298,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const clientX = e.clientX;
if (rect) {
this._hoverTime = this.toTimeline(clientX - rect.x, rect.width);
- if (this.dataDoc.thumbnails) {
+ if (this.thumbnails) {
const nearest = Math.floor((this._hoverTime / this.props.rawDuration) * VideoBox.numThumbnails);
- const thumbnails = StrListCast(this.dataDoc.thumbnails);
- const imgField = thumbnails?.length > 0 ? new ImageField(thumbnails[nearest]) : undefined;
+ const imgField = this.thumbnails.length > 0 ? new ImageField(this.thumbnails[nearest]) : undefined;
this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_m.png') : undefined;
}
}
@@ -388,19 +396,20 @@ 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,
_stayInCollection: true,
- useLinkSmallAnchor: true,
- hideLinkButton: true,
- _isLinkButton: true,
+ backgroundColor: 'rgba(128, 128, 128, 0.5)',
+ layout_hideLinkButton: true,
+ onClick: FollowLinkScript(),
annotationOn: rootDoc,
_timelineLabel: true,
borderRounding: anchorEndTime === undefined ? '100%' : undefined,
@@ -419,7 +428,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();
@@ -428,7 +437,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this.scrollToTime(seekTimeInSeconds);
}
} else {
- if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) {
+ if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) {
if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) {
this.props.Pause();
} else {
@@ -444,12 +453,12 @@ 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 (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._layout_currentTimecode) - 1e-4) {
if (this.props.playing()) this.props.Pause();
else if (this.layoutDoc.autoPlayAnchors) this.props.Play();
else if (!this.layoutDoc.autoPlayAnchors) {
@@ -505,37 +514,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 +539,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 +572,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}
@@ -624,6 +599,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
{!this.props.PanelHeight() ? null : (
<AudioWaveform
rawDuration={this.props.rawDuration}
+ fieldKey={this.props.dataFieldKey}
duration={this.clipDuration}
mediaPath={this.props.mediaPath}
layoutDoc={this.layoutDoc}
@@ -634,7 +610,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
PanelWidth={this.timelineContentWidth}
/>
)}
- {/* {this.renderDictation} */}
<div
className="collectionStackedTimeline-hover"
@@ -690,6 +665,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
interface StackedTimelineAnchorProps {
mark: Doc;
+ whenChildContentsActiveChanged: (isActive: boolean) => void;
+ addDocTab: (doc: Doc, where: OpenWhere) => boolean;
rangeClickScript: () => ScriptField;
rangePlayScript: () => ScriptField;
left: number;
@@ -738,19 +715,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.link_anchor_1, 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 +738,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 +758,8 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
}
return false;
};
+ this.noEvents = true;
var undo: UndoManager.Batch | undefined;
-
setupMoveUpEvents(
this,
e,
@@ -789,11 +768,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 +790,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}
@@ -831,9 +814,14 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
isDocumentActive={this.props.isDocumentActive}
PanelWidth={width}
PanelHeight={height}
- fitWidth={returnTrue}
+ layout_fitWidth={returnTrue}
ScreenToLocalTransform={screenXf}
+ pinToPres={emptyFunction}
focus={focusFunc}
+ isContentActive={returnFalse}
+ searchFilterDocs={returnEmptyDoclist}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
rootSelected={returnFalse}
onClick={script}
onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
@@ -854,15 +842,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.scss b/src/client/views/collections/CollectionStackingView.scss
index f3397e2c4..99a68e94b 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -372,7 +372,8 @@
.editableView-container-editing-oneLine,
.editableView-container-editing {
color: grey;
- padding-top: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
width: 100%;
}
@@ -387,7 +388,8 @@
letter-spacing: 2px;
color: grey;
border: 0px;
- padding-top: 10px; // 12px 10px 11px 10px;
+ padding-top: 5px; // 12px 10px 11px 10px;
+ padding-bottom: 5px; // 12px 10px 11px 10px;
}
}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 2f495d55c..e2e352857 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, returnNone, 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';
@@ -36,7 +36,7 @@ export type collectionStackingViewProps = {
sortFunc?: (a: Doc, b: Doc) => number;
chromeHidden?: boolean;
// view type is stacking
- viewType?: CollectionViewType;
+ type_collection?: CollectionViewType;
NativeWidth?: () => number;
NativeHeight?: () => number;
};
@@ -49,7 +49,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// Not sure what a pivot field is. Seems like we cause reaction in MobX get rid of it once we exit this view
_pivotFieldDisposer?: IReactionDisposer;
// Seems like we cause reaction in MobX get rid of our height once we exit this view
- _autoHeightDisposer?: IReactionDisposer;
+ _layout_autoHeightDisposer?: IReactionDisposer;
// keeping track of documents. Updated on internal and external drops. What's the difference?
_docXfs: { height: () => number; width: () => number; stackedDocTransform: () => Transform }[] = [];
// Doesn't look like this field is being used anywhere. Obsolete?
@@ -65,8 +65,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
}
// it looks like this gets the column headers that Mehek was showing just now
- @computed get columnHeaders() {
- return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null);
+ @computed get colHeaderData() {
+ return Cast(this.layoutDoc['_' + this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null);
}
// Still not sure what a pivot is, but it appears that we can actually filter docs somehow?
@computed get pivotField() {
@@ -94,7 +94,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
// are we stacking or masonry?
@computed get isStackingView() {
- return (this.props.viewType ?? this.layoutDoc._viewType) === CollectionViewType.Stacking;
+ return (this.props.type_collection ?? this.layoutDoc._type_collection) === CollectionViewType.Stacking;
}
// this is the number of StackingViewFieldColumns that we have
@computed get numGroupColumns() {
@@ -116,10 +116,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
constructor(props: any) {
super(props);
- if (this.columnHeaders === undefined) {
+ if (this.colHeaderData === undefined) {
// TODO: what is a layout doc? Is it literally how this document is supposed to be layed out?
// here we're making an empty list of column headers (again, what Mehek showed us)
- this.layoutDoc._columnHeaders = new List<SchemaHeaderField>();
+ this.layoutDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>();
}
}
@@ -129,6 +129,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
TraceMobx();
// appears that we are going to reset the _docXfs. TODO: what is Xfs?
this._docXfs.length = 0;
+ this._renderCount < docs.length && setTimeout(action(() => (this._renderCount = Math.min(docs.length, this._renderCount + 5))));
return docs.map((d, i) => {
const height = () => this.getDocHeight(d);
const width = () => this.getDocWidth(d);
@@ -139,7 +140,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// So we're choosing whether we're going to render a column or a masonry doc
return (
<div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}>
- {this.getDisplayDoc(d, width)}
+ {this.getDisplayDoc(d, width, i)}
</div>
);
});
@@ -153,14 +154,14 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
//TODO: this seems important
get Sections() {
// appears that pivot field IS actually for sorting
- if (!this.pivotField || this.columnHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
+ if (!this.pivotField || this.colHeaderData instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
- if (this.columnHeaders === undefined) {
- setTimeout(() => (this.layoutDoc._columnHeaders = new List<SchemaHeaderField>()), 0);
+ if (this.colHeaderData === undefined) {
+ setTimeout(() => (this.layoutDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>()), 0);
return new Map<SchemaHeaderField, Doc[]>();
}
- const columnHeaders = Array.from(this.columnHeaders);
- const fields = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
+ const colHeaderData = Array.from(this.colHeaderData);
+ const fields = new Map<SchemaHeaderField, Doc[]>(colHeaderData.map(sh => [sh, []] as [SchemaHeaderField, []]));
let changed = false;
this.filteredChildren.map(d => {
const sectionValue = (d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`) as object;
@@ -169,13 +170,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const castedSectionValue = !isNaN(parsed) ? parsed : sectionValue;
// look for if header exists already
- const existingHeader = columnHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`));
+ const existingHeader = colHeaderData.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`));
if (existingHeader) {
fields.get(existingHeader)!.push(d);
} else {
const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`);
fields.set(newSchemaHeader, [d]);
- columnHeaders.push(newSchemaHeader);
+ colHeaderData.push(newSchemaHeader);
changed = true;
}
});
@@ -186,13 +187,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
.filter(key => !fields.get(key)!.length)
.map(header => {
fields.delete(header);
- columnHeaders.splice(columnHeaders.indexOf(header), 1);
+ colHeaderData.splice(colHeaderData.indexOf(header), 1);
changed = true;
});
}
changed &&
setTimeout(
- action(() => this.columnHeaders?.splice(0, this.columnHeaders.length, ...columnHeaders)),
+ action(() => this.colHeaderData?.splice(0, this.colHeaderData.length, ...colHeaderData)),
0
);
return fields;
@@ -200,16 +201,17 @@ 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(
() => this.pivotField,
- () => (this.layoutDoc._columnHeaders = new List())
+ () => (this.layoutDoc['_' + this.fieldKey + '_columnHeaders'] = new List())
);
- this._autoHeightDisposer = reaction(
- () => this.layoutDoc._autoHeight,
- autoHeight =>
- autoHeight &&
+ this._layout_autoHeightDisposer = reaction(
+ () => this.layoutDoc._layout_autoHeight,
+ layout_autoHeight =>
+ layout_autoHeight &&
this.props.setHeight?.(
Math.min(
NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
@@ -222,11 +224,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
componentWillUnmount() {
super.componentWillUnmount();
this._pivotFieldDisposer?.();
- this._autoHeightDisposer?.();
+ this._layout_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) => {
@@ -234,9 +238,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
};
- @computed get onChildClickHandler() {
- return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
- }
+ onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
@computed get onChildDoubleClickHandler() {
return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
}
@@ -249,20 +251,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) => {
@@ -283,12 +282,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (docView && ['Enter'].includes(e.key) && e.ctrlKey) {
e.stopPropagation?.();
const below = !e.altKey && e.key !== 'Tab';
- const layoutKey = StrCast(docView.LayoutFieldKey);
+ const layout_fieldKey = StrCast(docView.LayoutFieldKey);
const newDoc = Doc.MakeCopy(docView.rootDoc, true);
const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
- if (layoutKey !== 'layout' && docView.rootDoc[layoutKey] instanceof Doc) {
- newDoc[layoutKey] = docView.rootDoc[layoutKey];
+ if (layout_fieldKey !== 'layout' && docView.rootDoc[layout_fieldKey] instanceof Doc) {
+ newDoc[layout_fieldKey] = docView.rootDoc[layout_fieldKey];
}
Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
@@ -297,20 +296,27 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
};
isContentActive = () => (this.props.isSelected() || this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined);
+ @observable _renderCount = 5;
isChildContentActive = () =>
- this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined;
+ this.props.isContentActive?.() === false
+ ? false
+ : this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive))
+ ? true
+ : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false
+ ? false
+ : undefined;
isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined);
// this is what renders the document that you see on the screen
// called in Children: this actually adds a document to our children list
- getDisplayDoc(doc: Doc, width: () => number) {
- const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
+ getDisplayDoc(doc: Doc, width: () => number, count: number) {
+ const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc;
const height = () => this.getDocHeight(doc);
let dref: Opt<DocumentView>;
const stackedDocTransform = () => this.getDocTransform(doc, dref);
this._docXfs.push({ stackedDocTransform, width, height });
//DocumentView is how the node will be rendered
- return (
+ return count > this._renderCount ? null : (
<DocumentView
ref={r => (dref = r || undefined)}
Document={doc}
@@ -318,20 +324,22 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
renderDepth={this.props.renderDepth + 1}
PanelWidth={width}
PanelHeight={height}
+ pointerEvents={this.props.DocumentView?.().props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
styleProvider={this.styleProvider}
docViewPath={this.props.docViewPath}
- fitWidth={this.props.childFitWidth}
- isContentActive={doc.isLinkButton ? this.isChildButtonContentActive : this.isChildContentActive}
+ layout_fitWidth={this.props.childLayoutFitWidth}
+ 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}
- NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
- NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeHeight(doc)) ? height : undefined}
+ NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (doc._layout_fitWidth && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
+ NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (doc._layout_fitWidth && !Doc.NativeHeight(doc)) ? height : undefined}
dontCenter={this.props.childIgnoreNativeSize ? 'xy' : undefined}
- dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't autoHeight resize if dontRegisterView is set, but they need to.
+ dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to.
rootSelected={this.rootSelected}
- showTitle={this.props.childShowTitle}
+ layout_showTitle={this.props.childlayout_showTitle}
dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
@@ -343,8 +351,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}
@@ -370,7 +376,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (!d) return 0;
const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
const maxWidth = this.columnWidth / this.numGroupColumns;
- if (!this.layoutDoc._columnsFill && !(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d))) {
+ if (!this.layoutDoc._columnsFill && !(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d))) {
return Math.min(d[WidthSym](), maxWidth);
}
return maxWidth;
@@ -378,17 +384,17 @@ 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);
+ const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[WidthSym]() : 0);
+ const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[HeightSym]() : 0);
if (nw && nh) {
const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
return Math.min(maxHeight, (docWid * nh) / nw);
}
const childHeight = NumCast(childLayoutDoc._height);
- const panelHeight = childLayoutDoc._fitWidth || this.props.childFitWidth?.(d) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
+ const panelHeight = childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
return Math.min(childHeight, maxHeight, panelHeight);
}
@@ -454,10 +460,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
docs.splice(insertInd - offset, 0, ...newDocs);
}
}
- } 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' });
+ } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
+ const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_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(), { link_relationship: '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,7 +526,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this.refList.push(ref);
this.observer = new _global.ResizeObserver(
action((entries: any) => {
- if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
+ if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
this.props.setHeight?.(height);
@@ -533,7 +539,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}}
addDocument={this.addDocument}
chromeHidden={this.chromeHidden}
- columnHeaders={this.columnHeaders}
+ colHeaderData={this.colHeaderData}
Document={this.props.Document}
DataDoc={this.props.DataDoc}
renderChildren={this.children}
@@ -575,7 +581,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this.refList.push(ref);
this.observer = new _global.ResizeObserver(
action((entries: any) => {
- if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
+ if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0);
this.props.setHeight?.(this.headerMargin + height);
}
@@ -602,9 +608,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
/// add a new group category (column) to the active set of note categories. (e.g., if the pivot field is 'transportation', groups might be 'car', 'plane', 'bike', etc)
@action
addGroup = (value: string) => {
- if (value && this.columnHeaders) {
+ if (value && this.colHeaderData) {
const schemaHdrField = new SchemaHeaderField(value);
- this.columnHeaders.push(schemaHdrField);
+ this.colHeaderData.push(schemaHdrField);
return true;
}
return false;
@@ -620,11 +626,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._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_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' });
}
};
@@ -652,11 +660,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}
@@ -672,8 +681,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>
);
@@ -717,28 +724,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/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index d62c4dc62..6be9cb72d 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -34,7 +34,7 @@ interface CSVFieldColumnProps {
heading: string;
pivotField: string;
chromeHidden?: boolean;
- columnHeaders: SchemaHeaderField[] | undefined;
+ colHeaderData: SchemaHeaderField[] | undefined;
headingObject: SchemaHeaderField | undefined;
yMargin: number;
columnWidth: number;
@@ -93,7 +93,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@undoBatch
columnDrop = action((e: Event, de: DragManager.DropEvent) => {
const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
- drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false));
+ this.props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false));
});
getValue = (value: string): any => {
const parsed = parseInt(value);
@@ -107,7 +107,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
headingChanged = (value: string, shiftDown?: boolean) => {
const castedValue = this.getValue(value);
if (castedValue) {
- if (this.props.columnHeaders?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
+ if (this.props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
return false;
}
this.props.docList.forEach(d => (d[this.props.pivotField] = castedValue));
@@ -128,13 +128,13 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4');
@action pointerLeave = () => (this._background = 'inherit');
- textCallback = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+ @undoBatch typedNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
@action
addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
if (!value && !forceEmptyNote) return false;
const key = this.props.pivotField;
- const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _fitWidth: true, title: value, _autoHeight: true });
+ const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
newDoc[key] = this.getValue(this.props.heading);
const maxHeading = this.props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
@@ -147,9 +147,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action
deleteColumn = () => {
this.props.docList.forEach(d => (d[this.props.pivotField] = undefined));
- if (this.props.columnHeaders && this.props.headingObject) {
- const index = this.props.columnHeaders.indexOf(this.props.headingObject);
- this.props.columnHeaders.splice(index, 1);
+ if (this.props.colHeaderData && this.props.headingObject) {
+ const index = this.props.colHeaderData.indexOf(this.props.headingObject);
+ this.props.colHeaderData.splice(index, 1);
}
};
@@ -163,15 +163,15 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
//TODO: I think this is where I'm supposed to edit stuff
startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
- // is MakeAlias a way to make a copy of a doc without rendering it?
- const alias = Doc.MakeAlias(this.props.Document);
- alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1);
- alias._pivotField = undefined;
+ // is MakeEmbedding a way to make a copy of a doc without rendering it?
+ const embedding = Doc.MakeEmbedding(this.props.Document);
+ embedding._width = this.props.columnWidth / (this.props.colHeaderData?.length || 1);
+ embedding._pivotField = undefined;
let value = this.getValue(this._heading);
value = typeof value === 'string' ? `"${value}"` : value;
- alias.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name });
- if (alias.viewSpecScript) {
- DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY);
+ embedding.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name });
+ if (embedding.viewSpecScript) {
+ DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([embedding]), e.clientX, e.clientY);
return true;
}
return false;
@@ -266,7 +266,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
!Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
Doc.GetProto(this.props.Document)[name] = '';
- const created = Docs.Create.TextDocument('', { title: name, _width: 250, _autoHeight: true });
+ const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true });
if (created) {
if (this.props.Document.isTemplateDoc) {
Doc.MakeMetadataFieldTemplate(created, this.props.Document);
@@ -363,7 +363,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
<EditableView
GetValue={returnEmptyString}
SetValue={this.addNewTextDoc}
- textCallback={this.textCallback}
+ textCallback={this.typedNote}
placeholder={"Type ':' for commands"}
contents={<FontAwesomeIcon icon={'plus'} />}
menuCallback={this.menuCallback}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 27ae3041f..cfe78afa1 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,12 +1,10 @@
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';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
-import { ScriptField } from '../../../fields/ScriptField';
import { Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
@@ -20,7 +18,6 @@ import { DocComponent } from '../DocComponent';
import React = require('react');
export interface SubCollectionViewProps extends CollectionViewProps {
- CollectionView: Opt<CollectionView>;
isAnyChildContentActive: () => boolean;
}
@@ -67,10 +64,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() {
- // 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>()));
- return this.dataDoc[this.props.fieldKey];
+ return this.layoutDoc[this.props.fieldKey];
}
get childLayoutPairs(): { layout: Doc; data: Doc }[] {
@@ -79,7 +73,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
}
@@ -111,9 +105,7 @@ export function CollectionSubView<X>(moreProps?: X) {
rawdocs = rootDoc && !this.props.isAnnotationOverlay ? [Doc.GetProto(rootDoc)] : [];
}
- const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.unrendered)).map(d => d as Doc);
- const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
- const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+ const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc);
const childDocFilters = this.childDocFilters();
const docRangeFilters = this.childDocRangeFilters();
@@ -127,25 +119,26 @@ export function CollectionSubView<X>(moreProps?: X) {
// dragging facets
const dragged = this.props.docFilters?.().some(f => f.includes(Utils.noDragsDocFilter));
if (dragged && DragManager.docsBeingDragged.includes(d)) return false;
- let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, viewSpecScript, this.props.Document).length > 0;
+ let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, this.props.Document).length > 0;
if (notFiltered) {
- notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0;
+ notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, this.props.Document).length > 0;
const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
- const data = d[annos ? fieldKey + '-annotations' : fieldKey];
- if (data !== undefined) {
- let subDocs = DocListCast(data);
+ const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name);
+ const data = d[annos ? fieldKey + '_annotations' : fieldKey];
+ const side = annos && d[fieldKey + '_sidebar'];
+ if (data !== undefined || side !== undefined) {
+ let subDocs = [...DocListCast(data), ...DocListCast(side)];
if (subDocs.length > 0) {
let newarray: Doc[] = [];
- notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, viewSpecScript, d).length);
+ notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, d).length);
while (subDocs.length > 0 && !notFiltered) {
newarray = [];
subDocs.forEach(t => {
const fieldKey = Doc.LayoutFieldKey(t);
- const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView');
- notFiltered =
- notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, viewSpecScript, d).length));
- DocListCast(t[annos ? fieldKey + '-annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
+ const annos = !Field.toString(Doc.LayoutField(t) as Field).includes(CollectionView.name);
+ notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, d).length));
+ DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
+ annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
});
subDocs = newarray;
}
@@ -192,17 +185,18 @@ export function CollectionSubView<X>(moreProps?: X) {
protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetAction: dropActionType) {
if (de.complete.docDragData) {
- // if targetDropAction is, say 'alias', but we're just dragging within a collection, we want to ignore the targetAction.
+ // if targetDropAction is, say 'embed', but we're just dragging within a collection, we want to ignore the targetAction.
// otherwise, the targetAction should become the actual action (which can still be overridden by the userDropAction -eg, shift/ctrl keys)
- if (targetAction && !de.complete.docDragData.draggedDocuments.some(d => d.context === this.props.Document && this.childDocs.includes(d))) {
+ if (targetAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.props.Document && this.childDocs.includes(d))) {
de.complete.docDragData.dropAction = targetAction;
}
e.stopPropagation();
}
}
- 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;
@@ -216,7 +210,7 @@ export function CollectionSubView<X>(moreProps?: X) {
const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
if (movedDocs.length) {
- const canAdd = this.props.Document._viewType === CollectionViewType.Pile || de.embedKey || this.props.Document.allowOverlayDrop || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document);
+ const canAdd = this.props.Document._type_collection === CollectionViewType.Pile || de.embedKey || this.props.Document.allowOverlayDrop || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document);
added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse);
} else {
ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
@@ -269,10 +263,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;
@@ -285,7 +279,7 @@ export function CollectionSubView<X>(moreProps?: X) {
addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
}
} else if (text) {
- addDocument(Docs.Create.TextDocument(text, { ...options, _showTitle: StrCast(Doc.UserDoc().showTitle), _width: 100, _height: 25 }));
+ addDocument(Docs.Create.TextDocument(text, { ...options, _layout_showTitle: StrCast(Doc.UserDoc().layout_showTitle), _width: 100, _height: 25 }));
}
return;
}
@@ -311,8 +305,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 +329,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, {});
}
}
}
@@ -348,7 +342,7 @@ export function CollectionSubView<X>(moreProps?: X) {
if ((uriList || text).includes('www.youtube.com/watch') || text.includes('www.youtube.com/embed')) {
const batch = UndoManager.StartBatch('youtube upload');
const generatedDocuments: Doc[] = [];
- this.slowLoadDocuments((uriList || text).split('v=')[1].split('&')[0], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end);
+ this.slowLoadDocuments((uriList || text).split('v=')[1].split('&')[0], options, generatedDocuments, text, completed, addDocument).then(batch.end);
return;
}
@@ -370,28 +364,17 @@ export function CollectionSubView<X>(moreProps?: X) {
// }
}
if (uriList) {
- // const existingWebDoc = await Hypothesis.findWebDoc(uriList);
- // if (existingWebDoc) {
- // const alias = Doc.MakeAlias(existingWebDoc);
- // alias.x = options.x;
- // alias.y = options.y;
- // alias._nativeWidth = 850;
- // alias._height = 512;
- // alias._width = 400;
- // addDocument(alias);
- // } else
- {
- const newDoc = Docs.Create.WebDocument(uriList.split('#annotations:')[0], {
+ addDocument(
+ Docs.Create.WebDocument(uriList.split('#annotations:')[0], {
// clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig)
...options,
title: uriList.split('#annotations:')[0],
_width: 400,
_height: 512,
_nativeWidth: 850,
- useCors: true,
- });
- addDocument(newDoc);
- }
+ data_useCors: true,
+ })
+ );
return;
}
@@ -431,25 +414,16 @@ export function CollectionSubView<X>(moreProps?: X) {
proto._height = 20;
return proto;
}),
- { _width: 150, _height: 600, title: 'across', backgroundColor: 'white', _singleLine: true }
+ { _width: 150, _height: 600, title: 'across', backgroundColor: 'white', _createDocOnCR: true }
)
);
});
}
}
- this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end);
+ this.slowLoadDocuments(files, options, generatedDocuments, text, completed, addDocument).then(batch.end);
}
- slowLoadDocuments = async (
- files: File[] | string,
- options: DocumentOptions,
- generatedDocuments: Doc[],
- text: string,
- completed: ((doc: Doc[]) => void) | undefined,
- clientX: number,
- clientY: number,
- addDocument: (doc: Doc | Doc[]) => boolean
- ) => {
+ slowLoadDocuments = async (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => {
// create placeholder docs
// inside placeholder docs have some func that
@@ -471,7 +445,7 @@ export function CollectionSubView<X>(moreProps?: X) {
}
if (generatedDocuments.length) {
// Creating a dash document
- const isFreeformView = this.props.Document._viewType === CollectionViewType.Freeform;
+ const isFreeformView = this.props.Document._type_collection === CollectionViewType.Freeform;
const set = !isFreeformView
? generatedDocuments
: generatedDocuments.length > 1
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 0ec21cc51..49a90d828 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -10,13 +10,13 @@ import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
import { Docs } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from '../EditableView';
-import { ViewSpecPrefix } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
+import { PresBox } from '../nodes/trails';
import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionSubView } from './CollectionSubView';
@@ -32,58 +32,45 @@ export class CollectionTimeView extends CollectionSubView() {
@observable _viewDefDivClick: Opt<ScriptField>;
@observable _focusPivotField: Opt<string>;
+ async componentDidMount() {
+ this.props.setContentView?.(this);
+ //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), "");
+ ///const childText = "const embedding = getEmbedding(self); switchView(embedding, detailView); embedding.dropAction='embed'; useRightSplit(embedding, shiftKey); ";
+ runInAction(() => {
+ this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name });
+ this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' });
+ });
+ }
+
+ get pivotField() {
+ return this._focusPivotField || StrCast(this.layoutDoc._pivotField);
+ }
+
getAnchor = (addAsAnnotation: boolean) => {
- const anchor = Docs.Create.HTMLAnchorDocument([], {
+ const anchor = Docs.Create.HTMLMarkerDocument([], {
title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
annotationOn: this.rootDoc,
});
-
- // save view spec information for anchor
- const proto = Doc.GetProto(anchor);
- proto.pivotField = this.pivotField;
- proto.docFilters = ObjectField.MakeCopy(this.layoutDoc._docFilters as ObjectField) || new List<string>([]);
- proto.docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField) || new List<string>([]);
- proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType;
+ PresBox.pinDocView(anchor, { pinData: { type_collection: 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
- if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) {
- Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor);
+ if (Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
+ Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
} else {
- this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]);
+ this.dataDoc[this.props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
}
}
return anchor;
};
- async componentDidMount() {
- this.props.setContentView?.(this);
- //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), "");
- ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; useRightSplit(alias, shiftKey); ";
- runInAction(() => {
- this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name });
- this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' });
- });
- }
-
- get pivotField() {
- return this._focusPivotField || StrCast(this.layoutDoc._pivotField);
- }
@action
- setViewSpec = (anchor: Doc, preview: boolean) => {
- if (preview) {
- // if in preview, then override document's fields with view spec
- this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters);
- this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters);
- this._focusPivotField = StrCast(anchor.pivotField);
- } else if (anchor.pivotField !== undefined) {
- // otherwise set document's fields based on anchor view spec
- this.layoutDoc._prevFilterIndex = 1;
- this.layoutDoc._pivotField = StrCast(anchor.pivotField);
- this.layoutDoc._docFilters = new List<string>(StrListCast(anchor.docFilters));
- this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters));
- }
- return 0;
+ 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;
@@ -153,7 +140,6 @@ export class CollectionTimeView extends CollectionSubView() {
}
};
- dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF;
@computed get contents() {
return (
<div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
@@ -162,8 +148,7 @@ export class CollectionTimeView extends CollectionSubView() {
engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }}
fitContentsToBox={returnTrue}
childClickScript={this._childClickedScript}
- viewDefDivClick={this._viewDefDivClick}
- //dontScaleFilter={this.dontScaleFilter}
+ viewDefDivClick={this.layoutEngine() === computeTimelineLayout.name ? undefined : this._viewDefDivClick}
layoutEngine={this.layoutEngine}
/>
</div>
@@ -217,7 +202,7 @@ export class CollectionTimeView extends CollectionSubView() {
this.childLayoutPairs.map(pair =>
this._allFacets
.filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof pair.layout[fieldKey] === 'number' || typeof pair.layout[fieldKey] === 'boolean' || typeof pair.layout[fieldKey] === 'string')
- .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey[0] !== '#' || fieldKey === '#') && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0]))
+ .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0]))
.map(fieldKey => keySet.add(fieldKey))
);
Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' }));
@@ -289,7 +274,7 @@ export class CollectionTimeView extends CollectionSubView() {
}
ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
- const pivotField = StrCast(pivotDoc._pivotField) || 'author';
+ const pivotField = StrCast(pivotDoc._pivotField, 'author');
let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField));
pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 1a265af4a..095e34c39 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[];
};
@@ -104,7 +104,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
componentDidMount() {
//this.props.setContentView?.(this);
this._disposers.autoheight = reaction(
- () => this.rootDoc.autoHeight,
+ () => this.rootDoc.layout_autoHeight,
auto => auto && this.computeHeight(),
{ fireImmediately: true }
);
@@ -114,25 +114,25 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
if (!this._isDisposing) {
const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace('px', ''));
const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()) + 6;
- this.layoutDoc._autoHeightMargins = bodyHeight;
+ this.layoutDoc._layout_autoHeightMargins = bodyHeight;
!this.props.dontRegisterView && this.props.setHeight?.(bodyHeight + titleHeight);
}
};
unobserveHeight = (ref: any) => {
this.refList.delete(ref);
- this.rootDoc.autoHeight && this.computeHeight();
+ this.rootDoc.layout_autoHeight && this.computeHeight();
};
observeHeight = (ref: any) => {
if (ref) {
this.refList.add(ref);
this.observer = new _global.ResizeObserver(
action((entries: any) => {
- if (this.rootDoc.autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) {
+ if (this.rootDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) {
this.computeHeight();
}
})
);
- this.rootDoc.autoHeight && this.computeHeight();
+ this.rootDoc.layout_autoHeight && this.computeHeight();
this.observer.observe(ref);
}
};
@@ -144,7 +144,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
const dragData = de.complete.docDragData;
if (dragData) {
- const isInTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.props.Document) || dragData.draggedDocuments.some(d => d.context === this.doc && this.childDocs.includes(d));
+ const isInTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.props.Document) || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d));
dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? 'same' : dragData.dropAction;
}
};
@@ -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;
}
@@ -176,7 +176,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
const doAddDoc = (doc: Doc | Doc[]) =>
(doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => {
const res = flg && Doc.AddDocToList(this.doc[DataSym], this.props.fieldKey, doc, relativeTo, before);
- res && (doc.context = this.props.Document);
+ res && (doc.embedContainer = this.props.Document);
return res;
}, true);
if (this.doc.resolvedDataDoc instanceof Promise) return false;
@@ -229,12 +229,12 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return (
<FormattedTextBox
{...this.props}
- fieldKey={'text'}
+ fieldKey="text"
renderDepth={this.props.renderDepth + 1}
isContentActive={this.isContentActive}
isDocumentActive={this.isContentActive}
rootSelected={returnTrue}
- forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not autoHeight
+ forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not layout_autoHeight
PanelWidth={this.documentTitleWidth}
PanelHeight={this.documentTitleHeight}
NativeDimScaling={returnOne}
@@ -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}
@@ -259,18 +257,20 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
};
headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields);
+ @observable _renderCount = 1;
@computed get treeViewElements() {
TraceMobx();
const dropAction = StrCast(this.doc.childDropAction) as dropActionType;
const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false;
+ if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
return TreeView.GetChildElements(
this.treeChildren,
this,
this,
this.doc,
this.props.DataDoc,
- this.props.ContainingCollectionDoc,
+ undefined,
undefined,
addDoc,
this.remove,
@@ -296,7 +296,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
//TODO: [AL] add these
this.props.AddToMap,
this.props.RemFromMap,
- this.props.hierarchyIndex
+ this.props.hierarchyIndex,
+ this._renderCount
);
}
@computed get titleBar() {
@@ -328,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}
@@ -344,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>
);
@@ -370,23 +369,23 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
marginTop = () => NumCast(this.doc._yMargin);
marginBot = () => NumCast(this.doc._yMargin);
documentTitleWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.panelWidth());
- documentTitleHeight = () => (this.layoutDoc?.[HeightSym]() || 0) - NumCast(this.layoutDoc.autoHeightMargins);
+ documentTitleHeight = () => (this.layoutDoc?.[HeightSym]() || 0) - NumCast(this.layoutDoc.layout_autoHeightMargins);
truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
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}
@@ -425,22 +424,26 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
</div>
</div>
</div>
- </div>,
- ];
- };
+ </div>
+ );
+ }
render() {
TraceMobx();
- const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1) || 1;
+ const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1) || 1;
return (
<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}
- fieldKey={this.props.fieldKey + '-annotations'}
+ fieldKey={this.props.fieldKey + '_annotations'}
dropAction={'move'}
select={emptyFunction}
addDocument={this.addAnnotationDocument}
@@ -448,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 a28b1ca19..7913d3188 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,9 +45,10 @@ 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)
- childFitWidth?: (child: Doc) => boolean;
- childShowTitle?: () => string;
+ childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
+ childContentsActive?: () => boolean | undefined;
+ childLayoutFitWidth?: (child: Doc) => boolean;
+ childlayout_showTitle?: () => string;
childOpacity?: () => number;
childContextMenuItems?: () => { script: ScriptField; label: string }[];
childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children
@@ -64,8 +63,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_> {}
@@ -88,7 +87,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
}
get collectionViewType(): CollectionViewType | undefined {
- const viewField = StrCast(this.layoutDoc._viewType);
+ const viewField = StrCast(this.layoutDoc._type_collection);
if (CollectionView._safeMode) {
switch (viewField) {
case CollectionViewType.Freeform:
@@ -101,18 +100,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 +127,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: (type_collection: CollectionViewType) => Doc) {
+ if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._type_collection !== 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)._layout_autoHeight = true), icon: 'ellipsis-v' },
+ { description: 'Notetaking', event: () => (func(CollectionViewType.NoteTaking)._layout_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' },
+ ];
- if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) {
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 +155,15 @@ 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;
+ !Doc.noviceMode &&
+ this.setupViewTypes('UI Controls...', vtype => {
+ const newRendition = Doc.MakeEmbedding(this.rootDoc);
+ newRendition._type_collection = vtype;
this.props.addDocTab(newRendition, OpenWhere.addRight);
return newRendition;
- },
- false
- );
+ });
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
@@ -191,25 +174,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' });
@@ -225,9 +190,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
description: `Edit ${func.name} script`,
icon: 'edit',
event: (obj: any) => {
- const alias = Doc.MakeAlias(this.rootDoc);
- DocUtils.makeCustomViewClicked(alias, undefined, func.key);
- this.props.addDocTab(alias, OpenWhere.addRight);
+ const embedding = Doc.MakeEmbedding(this.rootDoc);
+ DocUtils.makeCustomViewClicked(embedding, undefined, func.key);
+ this.props.addDocTab(embedding, OpenWhere.addRight);
},
})
);
@@ -255,11 +220,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);
- }
-
- isContentActive = (outsideReaction?: boolean) => this.props.isContentActive();
+ isContentActive = (outsideReaction?: boolean) => this.props.isContentActive() || this.isAnyChildContentActive();
render() {
TraceMobx();
@@ -275,14 +236,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?._type_collection === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? 'none' : undefined }}>
{this.renderSubView(this.collectionViewType, props)}
</div>
);
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index 0d045bada..58605c3f4 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -1,5 +1,9 @@
-@import "../global/globalCssVariables.scss";
+@import '../global/globalCssVariables.scss';
+.tabDocView-content {
+ height: 100%;
+ width: 100%;
+}
input.lm_title:focus,
input.lm_title {
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 25fccd89c..b20d05433 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -10,24 +10,23 @@ 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';
-import { DocUtils } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { undoable, UndoManager } from '../../util/UndoManager';
import { DashboardView } from '../DashboardView';
import { Colors, Shadows } from '../global/globalEnums';
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 +38,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
interface TabDocViewProps {
documentId: FieldId;
+ keyValue?: boolean;
glContainer: any;
}
@observer
@@ -57,7 +57,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() {
@@ -115,15 +115,13 @@ export class TabDocView extends React.Component<TabDocViewProps> {
titleEle.size = StrCast(doc.title).length + 3;
titleEle.value = doc.title;
- titleEle.onkeydown = (e: KeyboardEvent) => {
- e.stopPropagation();
- };
- titleEle.onchange = undoBatch(
- action((e: any) => {
+ titleEle.onkeydown = (e: KeyboardEvent) => e.stopPropagation();
+ titleEle.onchange = (e: any) => {
+ undoable(() => {
titleEle.size = e.currentTarget.value.length + 3;
Doc.GetProto(doc).title = e.currentTarget.value;
- })
- );
+ }, 'edit tab title')();
+ };
if (tab.element[0].children[1].children.length === 1) {
iconWrap.className = 'lm_iconWrap lm_moreInfo';
@@ -197,7 +195,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
action(selected => {
if (selected) this._activated = true;
const toggle = tab.element[0].children[2].children[0] as HTMLInputElement;
- selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch');
+ if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
+ undoable(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch')();
+ }
toggle.style.fontWeight = selected ? 'bold' : '';
// toggle.style.textTransform = selected ? "uppercase" : "";
}),
@@ -233,7 +233,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) {
const docList = docs instanceof Doc ? [docs] : docs;
- const batch = UndoManager.StartBatch('pinning doc');
+ const batch = UndoManager.StartBatch('Pin doc to pres trail');
const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
if (!Doc.ActivePresentation) {
@@ -247,15 +247,15 @@ 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 = Doc.MakeAlias(anchorDoc ?? 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.data = new List<Doc>(); // the children of the embedding's layout are the presentation slide children. the embedding's 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;
+ pinDoc.embedContainer = 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
pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
pinDoc.treeViewHeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling.
@@ -263,21 +263,15 @@ 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);
+ const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null);
+ if (pinProps.pinViewport) PresBox.pinDocView(pinDoc, pinProps, anchorDoc ?? doc);
if (!pinProps?.audioRange && duration !== undefined) {
pinDoc.mediaStart = 'manual';
pinDoc.mediaStop = 'manual';
pinDoc.presStartTime = NumCast(doc.clipStart);
pinDoc.presEndTime = NumCast(doc.clipEnd, duration);
}
- PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, pinDoc);
- 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)';
@@ -288,24 +282,24 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc.title = doc.title + ' (move)';
pinDoc.presMovement = PresMovement.Pan;
}
- if (pinDoc.isInkMask) {
+ if (pinDoc.stroke_isInkMask) {
pinDoc.presHideAfter = true;
pinDoc.presHideBefore = true;
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
});
- if (
+ if ( // open the presentation trail if it's not already opened
!Array.from(CollectionDockingView.Instance?.tabMap ?? [])
.map(d => d.DashDoc)
.includes(curPres)
) {
- const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []);
- if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1);
+ if (Doc.IsInMyOverlay(curPres)) Doc.RemFromMyOverlay(curPres);
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 +344,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 whereFields = doc._type_collection === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':');
+ const keyValue = whereFields[1]?.includes('KeyValue');
+ const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none;
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, undefined, this.addDocTab);
+ case undefined:
+ case OpenWhere.lightbox: if (this.layoutDoc?._isLightbox) {
+ const lightboxView = !doc.annotationOn && DocCast(doc.embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.embedContainer)) : 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[]) => {
@@ -378,21 +381,19 @@ export class TabDocView extends React.Component<TabDocViewProps> {
};
getCurrentFrame = () => {
- return NumCast(Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null)._currentFrame);
+ return NumCast(Cast(PresBox.Instance.activeItem.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) => {
- const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap;
- if (options?.willPanZoom !== false && shrinkwrap && this._document) {
- const focusSpeed = options.zoomTime ?? 500;
- shrinkwrap();
- this._view?.setViewTransition('transform', focusSpeed, () => options?.afterFocus?.(false));
- } else {
- 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;
@@ -405,8 +406,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
PanelHeight = () => this._panelHeight;
miniMapColor = () => this.tabColor;
tabView = () => this._view;
- disableMinimap = () => !this._document || this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._viewType !== CollectionViewType.Freeform;
- hideMinimap = () => this.disableMinimap() || BoolCast(this._document?.hideMinimap);
+ disableMinimap = () => !this._document || this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._type_collection !== CollectionViewType.Freeform;
+ hideMinimap = () => this.disableMinimap() || BoolCast(this._document?.layout_hideMinimap);
@computed get docView() {
return !this._activated || !this._document ? null : (
@@ -419,12 +420,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}
@@ -444,19 +447,19 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinToPres={TabDocView.PinDoc}
/>
<TabMinimapView key="minimap" hideMinimap={this.hideMinimap} addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />
- <Tooltip key="ttip" title={<div className="dash-tooltip">{this._document.hideMinimap ? 'Open minimap' : 'Close minimap'}</div>}>
+ <Tooltip key="ttip" title={<div className="dash-tooltip">{this._document.layout_hideMinimap ? 'Open minimap' : 'Close minimap'}</div>}>
<div
className="miniMap-hidden"
style={{
- display: this.disableMinimap() || this._document._viewType !== 'freeform' ? 'none' : undefined,
- color: this._document.hideMinimap ? Colors.BLACK : Colors.WHITE,
- backgroundColor: this._document.hideMinimap ? Colors.LIGHT_GRAY : Colors.MEDIUM_BLUE,
- boxShadow: this._document.hideMinimap ? Shadows.STANDARD_SHADOW : undefined,
+ display: this.disableMinimap() || this._document._type_collection !== 'freeform' ? 'none' : undefined,
+ color: this._document.layout_hideMinimap ? Colors.BLACK : Colors.WHITE,
+ backgroundColor: this._document.layout_hideMinimap ? Colors.LIGHT_GRAY : Colors.MEDIUM_BLUE,
+ boxShadow: this._document.layout_hideMinimap ? Shadows.STANDARD_SHADOW : undefined,
}}
onPointerDown={e => e.stopPropagation()}
onClick={action(e => {
e.stopPropagation();
- this._document!.hideMinimap = !this._document!.hideMinimap;
+ this._document!.layout_hideMinimap = !this._document!.layout_hideMinimap;
})}>
<FontAwesomeIcon icon={'globe-asia'} size="lg" />
</div>
@@ -468,11 +471,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
render() {
return (
<div
- className="collectionDockingView-content"
+ className="tabDocView-content"
style={{
fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined,
- height: '100%',
- width: '100%',
}}
ref={ref => {
if ((this._mainCont = ref)) {
@@ -556,8 +557,8 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
this,
e,
action((e: PointerEvent, down: number[], delta: number[]) => {
- doc._panX = clamp(NumCast(doc._panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
- doc._panY = clamp(NumCast(doc._panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
+ doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
+ doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
return false;
}),
emptyFunction,
@@ -566,18 +567,15 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
};
render() {
if (!this.renderBounds) return null;
- const miniWidth = (this.props.PanelWidth() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim) * 100;
- const miniHeight = (this.props.PanelHeight() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim) * 100;
- const miniLeft = 50 + ((NumCast(this.props.document._panX) - this.renderBounds.cx) / this.renderBounds.dim) * 100 - miniWidth / 2;
- const miniTop = 50 + ((NumCast(this.props.document._panY) - this.renderBounds.cy) / this.renderBounds.dim) * 100 - miniHeight / 2;
+ const miniWidth = (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100;
+ const miniHeight = (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100;
+ const miniLeft = 50 + ((NumCast(this.props.document._freeform_) - this.renderBounds.cx) / this.renderBounds.dim) * 100 - miniWidth / 2;
+ const miniTop = 50 + ((NumCast(this.props.document._freeform_panY) - this.renderBounds.cy) / this.renderBounds.dim) * 100 - miniHeight / 2;
const miniSize = this.returnMiniSize();
return this.props.hideMinimap() ? null : (
<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
@@ -599,7 +597,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 bd326f917..f5877fa1a 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';
@@ -41,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;
@@ -65,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[];
}
@@ -95,13 +96,13 @@ export class TreeView extends React.Component<TreeViewProps> {
private _header: React.RefObject<HTMLDivElement> = React.createRef();
private _tref = React.createRef<HTMLDivElement>();
@observable _docRef: Opt<DocumentView>;
- private _selDisposer: Opt<IReactionDisposer>;
+ private _disposers: { [name: string]: IReactionDisposer } = {};
private _editTitleScript: (() => ScriptField) | undefined;
private _openScript: (() => ScriptField) | undefined;
private _treedropDisposer?: DragManager.DragDropDisposer;
get treeViewOpenIsTransient() {
- return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsPrototype(this.doc);
+ return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsDataProto(this.doc);
}
set treeViewOpen(c: boolean) {
if (this.treeViewOpenIsTransient) this._transientOpenState = c;
@@ -117,14 +118,14 @@ export class TreeView extends React.Component<TreeViewProps> {
return 'TreeView(' + this.props.document.title + ')';
} // this makes mobx trace() statements more descriptive
get defaultExpandedView() {
- return this.doc.viewType === CollectionViewType.Docking
+ return this.doc.type_collection === CollectionViewType.Docking
? this.fieldKey
: this.props.treeView.dashboardMode
? this.fieldKey
: this.props.treeView.fileSysMode
? this.doc.isFolder
? this.fieldKey
- : 'aliases' // for displaying
+ : 'embeddings' // for displaying
: this.props.treeView.outlineMode || this.childDocs
? this.fieldKey
: Doc.noviceMode
@@ -142,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];
@@ -159,46 +160,31 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get childLinks() {
return this.childDocList('links');
}
- @computed get childAliases() {
- return this.childDocList('aliases');
+ @computed get childEmbeddings() {
+ return this.childDocList('proto_embeddings');
}
@computed get childAnnos() {
- return this.childDocList(this.fieldKey + '-annotations');
+ return this.childDocList(this.fieldKey + '_annotations');
}
@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;
@@ -206,20 +192,21 @@ 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;
};
@action setEditTitle = (docView?: DocumentView) => {
- this._selDisposer?.();
+ this._disposers.selection?.();
if (!docView) {
this._editTitle = false;
} else if (docView.isSelected()) {
const doc = docView.Document;
SelectionManager.SelectSchemaViewDoc(doc);
this._editTitle = true;
- this._selDisposer = reaction(
+ this._disposers.selection = reaction(
() => SelectionManager.SelectedSchemaDoc(),
seldoc => seldoc !== doc && this.setEditTitle(undefined)
);
@@ -232,11 +219,13 @@ export class TreeView extends React.Component<TreeViewProps> {
if (this.props.document.isFolder || Doc.IsSystem(this.props.document)) {
this.treeViewOpen = !this.treeViewOpen;
} else {
- // choose an appropriate alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias
- const bestAlias =
- docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
- const nextBestAlias = DocListCast(this.props.document.aliases).find(doc => doc.author === Doc.CurrentUserEmail);
- this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), OpenWhere.lightbox);
+ // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding
+ const bestEmbedding =
+ docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document)
+ ? docView.props.Document
+ : DocListCast(this.props.document.proto_embeddings).find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail);
+ const nextBestEmbedding = DocListCast(this.props.document.proto_embeddings).find(doc => doc.author === Doc.CurrentUserEmail);
+ this.props.addDocTab(bestEmbedding ?? nextBestEmbedding ?? Doc.MakeEmbedding(this.props.document), OpenWhere.lightbox);
}
};
@@ -259,7 +248,8 @@ export class TreeView extends React.Component<TreeViewProps> {
};
componentWillUnmount() {
- this._selDisposer?.();
+ this._renderTimer && clearTimeout(this._renderTimer);
+ Object.values(this._disposers).forEach(disposer => disposer?.());
this._treeEle && this.props.unobserveHeight(this._treeEle);
document.removeEventListener('pointermove', this.onDragMove, true);
document.removeEventListener('pointermove', this.onDragUp, true);
@@ -268,6 +258,10 @@ export class TreeView extends React.Component<TreeViewProps> {
}
componentDidUpdate() {
+ this._disposers.opening = reaction(
+ () => this.treeViewOpen,
+ open => !open && (this._renderCount = 20)
+ );
this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
}
@@ -313,28 +307,29 @@ 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,
treeViewExpandedView: 'data',
- _viewType: CollectionViewType.Tree,
- hideLinkButton: true,
- _showSidebar: true,
- _fitWidth: true,
+ _type_collection: CollectionViewType.Tree,
+ layout_hideLinkButton: true,
+ _layout_showSidebar: true,
+ _layout_fitWidth: true,
treeViewType: TreeViewType.outline,
x: 0,
y: 0,
_xMargin: 0,
_yMargin: 0,
- _autoHeight: true,
- _singleLine: true,
+ _layout_autoHeight: true,
+ _createDocOnCR: true,
_width: 1000,
_height: 10,
});
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;
}
@@ -366,26 +361,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, { link_relationship: '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.embedContainer = this.doc.embedContainer);
+ 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;
}
@@ -401,17 +408,17 @@ 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,
(() => {
const aspect = Doc.NativeAspect(layoutDoc);
if (aspect) return this.embeddedPanelWidth() / (aspect || 1);
- return layoutDoc._fitWidth
+ return layoutDoc._layout_fitWidth
? !Doc.NativeHeight(layoutDoc)
- ? NumCast(layoutDoc._height) //this.props.containerCollection._height)
- : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)))
+ ? NumCast(layoutDoc._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]();
})()
);
@@ -431,23 +438,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.embedContainer = this.doc.embedContainer);
+ 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,
@@ -512,11 +523,13 @@ export class TreeView extends React.Component<TreeViewProps> {
return rows;
}
+ _renderTimer: any;
+ @observable _renderCount = 1;
@computed get renderContent() {
TraceMobx();
const expandKey = this.treeViewExpandedView;
const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }) ?? {};
- if (['links', 'annotations', 'aliases', this.fieldKey].includes(expandKey)) {
+ if (['links', 'annotations', 'embeddings', this.fieldKey].includes(expandKey)) {
const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None);
const sortKeys = Object.keys(sortings);
const curSortIndex = Math.max(
@@ -525,6 +538,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
@@ -535,14 +549,24 @@ 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.embedContainer = this.doc.embedContainer);
+
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);
- const docs = expandKey === 'aliases' ? this.childAliases : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs;
+ const docs = expandKey === 'embeddings' ? this.childEmbeddings : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs;
let downX = 0,
downY = 0;
+ if (docs?.length && this._renderCount < docs?.length) {
+ this._renderTimer && clearTimeout(this._renderTimer);
+ this._renderTimer = setTimeout(
+ action(() => {
+ this._renderCount = Math.min(docs!.length, this._renderCount + 20);
+ })
+ );
+ }
return (
<>
{!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : (
@@ -551,6 +575,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' : ''}
@@ -573,11 +598,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,
@@ -599,14 +624,15 @@ export class TreeView extends React.Component<TreeViewProps> {
// TODO: [AL] add these
this.props.AddToMap,
this.props.RemFromMap,
- this.props.hierarchyIndex
+ this.props.hierarchyIndex,
+ this._renderCount
)}
</ul>
</>
);
} 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>
);
@@ -632,7 +658,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,
},
@@ -651,7 +677,7 @@ export class TreeView extends React.Component<TreeViewProps> {
return (
<div
className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`}
- key={'bullet'}
+ key="bullet"
title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : 'view fields'}
onClick={this.bulletClick}
style={
@@ -682,13 +708,13 @@ 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 annos = () => (DocListCast(this.doc[this.fieldKey + '_annotations']).length && !this.props.treeView.dashboardMode ? 'annotations' : '');
+ 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 embeddings = () => (this.props.treeView.dashboardMode ? '' : 'embeddings');
const fields = () => (Doc.noviceMode ? '' : 'fields');
- const layout = Doc.noviceMode || this.doc.viewType === CollectionViewType.Docking ? [] : ['layout'];
- return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
+ const layout = Doc.noviceMode || this.doc.type_collection === CollectionViewType.Docking ? [] : ['layout'];
+ return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m);
}
@action
expandNextviewType = () => {
@@ -700,9 +726,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"
@@ -733,7 +761,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 openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(self), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' };
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)),
@@ -742,10 +770,10 @@ export class TreeView extends React.Component<TreeViewProps> {
: Doc.IsSystem(this.doc)
? []
: this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc)
- ? [openAlias, makeFolder]
- : this.doc.viewType === CollectionViewType.Docking
+ ? [openEmbedding, makeFolder]
+ : this.doc.type_collection === CollectionViewType.Docking
? []
- : [deleteItem, openAlias, focusDoc]),
+ : [deleteItem, openEmbedding, focusDoc]),
];
};
childContextMenuItems = () => {
@@ -755,9 +783,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);
@@ -777,7 +803,6 @@ export class TreeView extends React.Component<TreeViewProps> {
case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1;
case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
case StyleProp.Highlighting: if (this.props.treeView.outlineMode) return undefined;
- case StyleProp.Hidden: return false;
case StyleProp.BoxShadow: return undefined;
case StyleProp.DocContents:
const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc);
@@ -803,7 +828,6 @@ export class TreeView extends React.Component<TreeViewProps> {
};
embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (property.startsWith(StyleProp.Decorations)) return null;
- if (property.startsWith(StyleProp.Hidden)) return false;
return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
};
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
@@ -874,13 +898,15 @@ export class TreeView extends React.Component<TreeViewProps> {
}
})}
Document={this.doc}
- fitWidth={returnTrue}
+ layout_fitWidth={returnTrue}
DataDoc={undefined}
scriptContext={this}
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}
@@ -911,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
@@ -932,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>
</>
);
@@ -957,6 +978,7 @@ export class TreeView extends React.Component<TreeViewProps> {
);
};
+ fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined);
renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => {
return (
<div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
@@ -965,6 +987,7 @@ export class TreeView extends React.Component<TreeViewProps> {
ref={action((r: DocumentView | null) => (this._dref = r))}
Document={this.doc}
DataDoc={undefined}
+ layout_fitWidth={this.fitWidthFilter}
PanelWidth={this.embeddedPanelWidth}
PanelHeight={this.embeddedPanelHeight}
LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
@@ -989,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}
@@ -1009,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.layout_unrendered && !this.doc.treeViewFieldKey ? (
+ <div></div>
+ ) : (
<>
{this.renderBullet}
{this.renderTitle}
@@ -1043,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() {
@@ -1101,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,
@@ -1127,22 +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[],
- hierarchyIndex?: number[]
+ 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) => {
- const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child);
+ if (renderCount && i > renderCount) return null;
+ const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child);
if (!pair.layout || pair.data instanceof Promise) {
return null;
}
@@ -1156,15 +1181,12 @@ export class TreeView extends React.Component<TreeViewProps> {
TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined;
Doc.AddDocToList(newParent, fieldKey, child, addAfter, false);
newParent.treeViewOpen = true;
- child.context = treeView.Document;
+ child.embedContainer = treeView.Document;
}
};
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);
@@ -1176,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}
@@ -1188,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..fee4705e6 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;
@@ -91,6 +93,7 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
width: layout[WidthSym](),
height: layout[HeightSym](),
pair: { layout, data },
+ transition: 'all .3s',
replica: '',
});
});
@@ -98,26 +101,28 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
}
export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
- const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel
const docMap = new Map<string, PoolData>();
- const docSize = mustFit ? panelDim[0] * 0.33 : 75; // assume an icon sized at 75
- const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize];
- const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize];
+ const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
+ const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
childPairs.forEach(({ layout, data }, i) => {
- const docSize = layout.layoutKey === 'layout_icon' ? (mustFit ? panelDim[0] * 0.33 : 75) : 400; // assume a icon sized at 75
+ const aspect = layout[HeightSym]() / layout[WidthSym]();
+ const docSize = Math.min(Math.min(400, layout[WidthSym]()), Math.min(400, layout[WidthSym]()) / aspect) * burstScale;
const deg = (i / childPairs.length) * Math.PI * 2;
docMap.set(layout[Id], {
- x: Math.cos(deg) * burstRadius[0] - docSize / 2,
- y: Math.sin(deg) * burstRadius[1] - (docSize * layout[HeightSym]()) / layout[WidthSym]() / 2,
- width: docSize, //layout[WidthSym](),
- height: (docSize * layout[HeightSym]()) / layout[WidthSym](),
+ x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)),
+ y: Math.min(burstDiam[1] / 2 - docSize * aspect, Math.max(-burstDiam[1] / 2, (Math.sin(deg) * burstDiam[1]) / 2 - (docSize / 2) * aspect)),
+ width: docSize,
+ height: docSize * aspect,
zIndex: NumCast(layout.zIndex),
pair: { layout, data },
replica: '',
+ color: 'white',
+ backgroundColor: 'white',
+ transition: 'all 0.3s',
});
});
- const divider = { type: 'div', color: 'transparent', x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined };
- return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
+ const divider = { type: 'div', color: 'transparent', x: -burstDiam[0] / 2, y: -burstDiam[1] / 2, width: 15, height: 15, payload: undefined };
+ return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
}
export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
@@ -128,20 +133,15 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
let nonNumbers = 0;
const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author';
childPairs.map(pair => {
- const lval =
- pivotFieldKey === '#' || pivotFieldKey === 'tags'
- ? Array.from(Object.keys(Doc.GetProto(pair.layout)))
- .filter(k => k.startsWith('#'))
- .map(k => k.substring(1))
- : Cast(pair.layout[pivotFieldKey], listSpec('string'), null);
+ const listValue = Cast(pair.layout[pivotFieldKey], listSpec('string'), null);
const num = toNumber(pair.layout[pivotFieldKey]);
if (num === undefined || Number.isNaN(num)) {
nonNumbers++;
}
const val = Field.toString(pair.layout[pivotFieldKey] as Field);
- if (lval) {
- lval.forEach((val, i) => {
+ if (listValue) {
+ listValue.forEach((val, i) => {
!pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
pivotColumnGroups.get(val)!.docs.push(pair.layout);
pivotColumnGroups.get(val)!.replicas.push(i.toString());
@@ -154,8 +154,8 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
docMap.set(pair.layout[Id], {
x: 0,
y: 0,
- zIndex: -99,
- width: 0,
+ zIndex: 0,
+ width: 0, // should make doc hidden in CollectionFreefromDocumentView
height: 0,
pair,
replica: '',
@@ -406,7 +406,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,8 +415,12 @@ 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,
};
+ if (newPosRaw.transition) newPos.transition = newPosRaw.transition;
poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos });
}
});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 9811c239b..68ba3d368 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,6 +1,6 @@
import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, Field } from '../../../../fields/Doc';
+import { CssSym, Doc, Field } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { Cast, NumCast, StrCast } from '../../../../fields/Types';
@@ -34,11 +34,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
this._anchorDisposer = reaction(
() => [
this.props.A.props.ScreenToLocalTransform(),
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?.scrollTop,
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?._highlights,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[CssSym],
this.props.B.props.ScreenToLocalTransform(),
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?.scrollTop,
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?._highlights,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[CssSym],
],
action(() => {
this._start = Date.now();
@@ -58,7 +58,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
0
); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(
- action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)),
+ action(() => (!LinkDocs.length || !linkDoc.layout_linkDisplay) && (this._opacity = 0.05)),
750
); // this will unhighlight the link line.
const a = A.ContentDiv.getBoundingClientRect();
@@ -72,36 +72,36 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
// if there's an element in the DOM with a classname containing a link anchor's id,
// then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
// otherwise, we just use the computed nearest point on the document boundary to the target Document
- const targetAhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.anchor1 as Doc)[Id])).lastElement();
- const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.anchor2 as Doc)[Id])).lastElement();
+ const targetAhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_1 as Doc)[Id])).lastElement();
+ const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_2 as Doc)[Id])).lastElement();
if ((!targetAhyperlink && !a.width) || (!targetBhyperlink && !b.width)) return;
if (!targetAhyperlink) {
- if (linkDoc.linkAutoMove) {
- linkDoc.anchor1_x = ((apt.point.x - aleft) / awidth) * 100;
- linkDoc.anchor1_y = ((apt.point.y - atop) / aheight) * 100;
+ if (linkDoc.layout_autoMoveAnchors) {
+ linkDoc.link_anchor_1_x = ((apt.point.x - aleft) / awidth) * 100;
+ linkDoc.link_anchor_1_y = ((apt.point.y - atop) / aheight) * 100;
}
} else {
const m = targetAhyperlink.getBoundingClientRect();
const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
const mpx = mp[0] / A.props.PanelWidth();
const mpy = mp[1] / A.props.PanelHeight();
- if (mpx >= 0 && mpx <= 1) linkDoc.anchor1_x = mpx * 100;
- if (mpy >= 0 && mpy <= 1) linkDoc.anchor1_y = mpy * 100;
+ if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_1_x = mpx * 100;
+ if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_1_y = mpy * 100;
if (getComputedStyle(targetAhyperlink).fontSize === '0px') linkDoc.opacity = 0;
else linkDoc.opacity = 1;
}
if (!targetBhyperlink) {
- if (linkDoc.linkAutoMove) {
- linkDoc.anchor2_x = ((bpt.point.x - bleft) / bwidth) * 100;
- linkDoc.anchor2_y = ((bpt.point.y - btop) / bheight) * 100;
+ if (linkDoc.layout_autoMoveAnchors) {
+ linkDoc.link_anchor_2_x = ((bpt.point.x - bleft) / bwidth) * 100;
+ linkDoc.link_anchor_2_y = ((bpt.point.y - btop) / bheight) * 100;
}
} else {
const m = targetBhyperlink.getBoundingClientRect();
const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
const mpx = mp[0] / B.props.PanelWidth();
const mpy = mp[1] / B.props.PanelHeight();
- if (mpx >= 0 && mpx <= 1) linkDoc.anchor2_x = mpx * 100;
- if (mpy >= 0 && mpy <= 1) linkDoc.anchor2_y = mpy * 100;
+ if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_2_x = mpx * 100;
+ if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_2_y = mpy * 100;
if (getComputedStyle(targetBhyperlink).fontSize === '0px') linkDoc.opacity = 0;
else linkDoc.opacity = 1;
}
@@ -112,8 +112,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
this,
e,
(e, down, delta) => {
- this.props.LinkDocs[0].linkOffsetX = NumCast(this.props.LinkDocs[0].linkOffsetX) + delta[0];
- this.props.LinkDocs[0].linkOffsetY = NumCast(this.props.LinkDocs[0].linkOffsetY) + delta[1];
+ this.props.LinkDocs[0].link_relationship_OffsetX = NumCast(this.props.LinkDocs[0].link_relationship_OffsetX) + delta[0];
+ this.props.LinkDocs[0].link_relationship_OffsetY = NumCast(this.props.LinkDocs[0].link_relationship_OffsetY) + delta[1];
return false;
},
emptyFunction,
@@ -136,6 +136,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
height = rect.height;
var el = el.parentNode;
while (el && el !== document.body) {
+ if (el.className === 'tabDocView-content') break;
rect = el.getBoundingClientRect?.();
if (rect?.width) {
if (top <= rect.bottom === false && getComputedStyle(el).overflow === 'hidden') return rect.bottom;
@@ -204,8 +205,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const bclipped = bleft !== b.left || btop !== b.top;
if (aclipped && bclipped) return undefined;
const clipped = aclipped || bclipped;
- const pt1inside = NumCast(LinkDocs[0].anchor1_x) % 100 !== 0 && NumCast(LinkDocs[0].anchor1_y) % 100 !== 0;
- const pt2inside = NumCast(LinkDocs[0].anchor2_x) % 100 !== 0 && NumCast(LinkDocs[0].anchor2_y) % 100 !== 0;
+ const pt1inside = NumCast(LinkDocs[0].link_anchor_1_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_1_y) % 100 !== 0;
+ const pt2inside = NumCast(LinkDocs[0].link_anchor_2_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_2_y) % 100 !== 0;
const pt1 = [aleft + a.width / 2, atop + a.height / 2];
const pt2 = [bleft + b.width / 2, btop + b.width / 2];
const pt2vec = pt2inside ? [-0.7071, 0.7071] : [(bDocBounds.left + bDocBounds.right) / 2 - pt2[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt2[1]];
@@ -222,8 +223,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const aActive = A.isSelected() || Doc.IsBrushed(A.rootDoc);
const bActive = B.isSelected() || Doc.IsBrushed(B.rootDoc);
- const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].linkOffsetX);
- const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].linkOffsetY);
+ const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX);
+ const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY);
return {
a,
b,
@@ -243,12 +244,12 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const link = this.props.LinkDocs[0];
const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
- const linkRelationship = Field.toString(link?.linkRelationship as any as Field); //get string representing relationship
- const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>;
+ const linkRelationship = Field.toString(link?.link_relationship as any as Field); //get string representing relationship
+ const linkRelationshipList = Doc.UserDoc().link_relationshipList as List<string>;
const linkColorList = Doc.UserDoc().linkColorList as List<string>;
- const linkRelationshipSizes = Doc.UserDoc().linkRelationshipSizes as List<number>;
+ const linkRelationshipSizes = Doc.UserDoc().link_relationshipSizes as List<number>;
const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship);
- const linkDescription = Field.toString(link.description as any as Field);
+ const linkDescription = Field.toString(link.link_description as any as Field);
const linkSize = currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex];
@@ -260,11 +261,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
//thickness varies linearly from 3px to 12px for increasing link count
const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px';
- if (link.linkDisplayArrow === undefined) {
- link.linkDisplayArrow = false;
+ if (link.layout_linkDisplayArrow === undefined) {
+ link.layout_linkDisplayArrow = false;
}
- return link.opacity === 0 || !a.width || !b.width || (!link.linkDisplay && !aActive && !bActive) ? null : (
+ return link.opacity === 0 || !a.width || !b.width || (!link.layout_linkDisplay && !aActive && !bActive) ? null : (
<>
<defs>
<marker id={`${link[Id] + 'arrowhead'}`} markerWidth="4" markerHeight="3" refX="0" refY="1.5" orient="auto">
@@ -293,7 +294,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
style={{ pointerEvents: 'visibleStroke', opacity: this._opacity, stroke, strokeWidth }}
onClick={this.onClickLine}
d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`}
- markerEnd={link.linkDisplayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''}
+ markerEnd={link.layout_linkDisplayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''}
/>
{textX === undefined || !linkDescription ? null : (
<text filter={`url(#${link[Id] + 'background'})`} className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 7a7ae3f40..c6419885b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -33,7 +33,7 @@
}
.collectionfreeformview-mask {
mix-blend-mode: multiply;
- background-color: rgba(0, 0, 0, 0.7);
+ background-color: rgba(0, 0, 0, 0.8);
}
.collectionfreeformview-viewdef {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 695d500e9..8b9698293 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, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
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, ViewSpecPrefix } 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';
@@ -55,13 +53,16 @@ import { MarqueeView } from './MarqueeView';
import React = require('react');
export type collectionFreeformViewProps = {
+ 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;
- dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them
+ getScrollHeight?: () => number | undefined;
dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.
// However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents.
};
@@ -75,7 +76,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;
@@ -95,14 +96,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 ?? '') + '_freeform_scale';
+ }
+ private get panXFieldKey() {
+ return (this.props.viewField ?? '') + '_freeform_panX';
+ }
+ private get panYFieldKey() {
+ return (this.props.viewField ?? '') + '_freeform_panY';
}
private get borderWidth() {
return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
@@ -112,7 +116,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[] = [];
@@ -124,7 +128,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
constructor(props: any) {
super(props);
- this.props.setContentView?.(this);
}
@computed get views() {
@@ -136,11 +139,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@computed get fitToContentVals() {
return {
bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 },
- scale: !this.childDocs.length ? 1 : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
+ scale:
+ !this.childDocs.length || !Number.isFinite(this.contentBounds.b - this.contentBounds.y) || !Number.isFinite(this.contentBounds.r - this.contentBounds.x)
+ ? 1
+ : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
};
}
@computed get fitContentsToBox() {
- return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox || this.Document.isGroup) && !this.isAnnotationOverlay;
+ return (this.props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
}
@computed get contentBounds() {
const cb = Cast(this.rootDoc.contentBounds, listSpec('number'));
@@ -148,27 +154,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
: this.props.contentBounds?.() ??
aggregateBounds(
- this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!),
+ this._layoutElements.filter(e => e.bounds && e.bounds.width && !e.bounds.z).map(e => e.bounds!),
NumCast(this.layoutDoc._xPadding, 10),
NumCast(this.layoutDoc._yPadding, 10)
);
}
@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
+ const aspect = dv?.nativeWidth && dv?.nativeHeight && !dv.layoutDoc.layout_fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
+ 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()
@@ -182,7 +188,32 @@ 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);
+ }
+ public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) {
+ if (timer) clearTimeout(timer);
+ const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, undefined, true);
+ const timecode = Math.round(time);
+ docs.forEach(doc => {
+ CollectionFreeFormDocumentView.animFields.forEach(val => {
+ const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null);
+ findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
+ });
+ CollectionFreeFormDocumentView.animStringFields.forEach(val => {
+ const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null);
+ findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
+ });
+ CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
+ const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null);
+ findexed?.length <= timecode + 1 && findexed.push(undefined as any);
+ });
+ });
+ 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) {
@@ -190,10 +221,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
if (back) {
- this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice());
+ this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], 1000);
this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
} else {
- this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0);
+ this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], currentFrame || 0);
this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
}
@@ -208,21 +239,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
shrinkWrap = () => {
if (this.props.DocumentView?.().nativeWidth) return;
const vals = this.fitToContentVals;
- this.layoutDoc._panX = vals.bounds.cx;
- this.layoutDoc._panY = vals.bounds.cy;
- this.layoutDoc._viewScale = vals.scale;
+ this.layoutDoc._freeform_panX = vals.bounds.cx;
+ this.layoutDoc._freeform_panY = vals.bounds.cy;
+ this.layoutDoc._freeform_scale = vals.scale;
};
freeformData = (force?: boolean) => (!this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined);
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.
+ // freeform_panx, freeform_pany, freeform_scale 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)?.freeform_panX, 1));
+ panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
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();
@@ -234,7 +263,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;
@@ -268,10 +297,44 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
isCurrent(doc: Doc) {
const dispTime = NumCast(doc._timecodeToShow, -1);
const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5);
- const curTime = NumCast(this.Document._currentTimecode, -1);
+ const curTime = NumCast(this.Document._layout_currentTimecode, -1);
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) => {
+ if (this._lightboxDoc) return;
+ const xfToCollection = options?.docTransform ?? Transform.Identity();
+ const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
+ const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+ 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 => {
+ if (doc.hidden && this._lightboxDoc !== doc) doc.hidden = false;
+ const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
+ findDoc(dv => res(dv));
+ });
+ };
+
@action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
if (!de.embedKey && !this.ChildDrag && this.rootDoc._isGroup) return false;
@@ -285,9 +348,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
.map(pair => pair.layout)
.slice()
.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
- zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1));
+ zsorted.forEach((doc, index) => (doc.zIndex = doc.stroke_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);
@@ -302,13 +366,35 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
d.x = x + NumCast(d.x) - dropPos[0];
d.y = y + NumCast(d.y) - dropPos[1];
}
- d._lastModified = new DateField();
+ d._layout_modificationDate = new DateField();
const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)];
layoutDoc._width = NumCast(layoutDoc._width, 300);
layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? (nd[1] / nd[0]) * NumCast(layoutDoc._width) : 300);
(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;
}
@@ -332,11 +418,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) {
// dragged document is a child of this collection
- if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.context !== this.props.Document) {
- // if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document
+ if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.embedContainer !== this.props.Document) {
+ // if the source doc view's embedContainer isn't this same freeformcollectionlinkDragData.dragDocument.embedContainer === 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, { link_relationship: '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;
@@ -360,13 +446,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return this.childLayoutPairs
.map(pair => pair.layout)
.reduce((cluster, cd) => {
- const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1);
+ const grouping = this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
if (grouping !== -1) {
const layoutDoc = Doc.Layout(cd);
- const cx = NumCast(cd.x) - this._clusterDistance;
- const cy = NumCast(cd.y) - this._clusterDistance;
- const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance;
- const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance;
+ const cx = NumCast(cd.x) - this._clusterDistance / 2;
+ const cy = NumCast(cd.y) - this._clusterDistance / 2;
+ const cw = NumCast(layoutDoc._width) + this._clusterDistance;
+ const ch = NumCast(layoutDoc._height) + this._clusterDistance;
return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
}
return cluster;
@@ -377,10 +463,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (cluster !== -1) {
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 eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
+ 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);
+ const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
de.moveDocument = this.props.moveDocument;
de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
DragManager.StartDocumentDrag(
@@ -397,10 +483,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return false;
}
- @undoBatch
@action
- updateClusters(_useClusters: boolean) {
- this.props.Document._useClusters = _useClusters;
+ updateClusters(_freeform_useClusters: boolean) {
+ this.props.Document._freeform_useClusters = _freeform_useClusters;
this._clusterSets.length = 0;
this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
}
@@ -408,70 +493,74 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
updateClusterDocs(docs: Doc[]) {
const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.props.Document._useClusters) {
+ if (this.props.Document._freeform_useClusters) {
const docFirst = docs[0];
docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
- const preferredInd = NumCast(docFirst.cluster);
- docs.map(doc => (doc.cluster = -1));
+ const preferredInd = NumCast(docFirst.layout_cluster);
+ docs.map(doc => (doc.layout_cluster = -1));
docs.map(doc =>
this._clusterSets.map((set, i) =>
set.map(member => {
- if (docFirst.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
- docFirst.cluster = i;
+ if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+ docFirst.layout_cluster = i;
}
})
)
);
- if (docFirst.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
- docFirst.cluster = preferredInd;
+ if (
+ docFirst.layout_cluster === -1 &&
+ preferredInd !== -1 &&
+ this._clusterSets.length > preferredInd &&
+ (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)
+ ) {
+ docFirst.layout_cluster = preferredInd;
}
this._clusterSets.map((set, i) => {
- if (docFirst.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
- docFirst.cluster = i;
+ if (docFirst.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
+ docFirst.layout_cluster = i;
}
});
- if (docFirst.cluster === -1) {
+ if (docFirst.layout_cluster === -1) {
docs.map(doc => {
- doc.cluster = this._clusterSets.length;
+ doc.layout_cluster = this._clusterSets.length;
this._clusterSets.push([doc]);
});
} else if (this._clusterSets.length) {
- for (let i = this._clusterSets.length; i <= NumCast(docFirst.cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
- docs.map(doc => this._clusterSets[(doc.cluster = NumCast(docFirst.cluster))].push(doc));
+ for (let i = this._clusterSets.length; i <= NumCast(docFirst.layout_cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
+ docs.map(doc => this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc));
}
- childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.cluster === i) && this.updateCluster(child));
+ childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.layout_cluster === i) && this.updateCluster(child));
}
}
- @undoBatch
@action
updateCluster(doc: Doc) {
const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.props.Document._useClusters) {
+ if (this.props.Document._freeform_useClusters) {
this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
- const preferredInd = NumCast(doc.cluster);
- doc.cluster = -1;
+ const preferredInd = NumCast(doc.layout_cluster);
+ doc.layout_cluster = -1;
this._clusterSets.forEach((set, i) =>
set.forEach(member => {
- if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
- doc.cluster = i;
+ if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+ doc.layout_cluster = i;
}
})
);
- if (doc.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
- doc.cluster = preferredInd;
+ if (doc.layout_cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
+ doc.layout_cluster = preferredInd;
}
this._clusterSets.forEach((set, i) => {
- if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
- doc.cluster = i;
+ if (doc.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
+ doc.layout_cluster = i;
}
});
- if (doc.cluster === -1) {
- doc.cluster = this._clusterSets.length;
+ if (doc.layout_cluster === -1) {
+ doc.layout_cluster = this._clusterSets.length;
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);
+ for (let i = this._clusterSets.length; i <= doc.layout_cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
+ this._clusterSets[doc.layout_cluster ?? 0].push(doc);
}
}
}
@@ -480,8 +569,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
switch (property) {
case StyleProp.BackgroundColor:
- const cluster = NumCast(doc?.cluster);
- if (this.Document._useClusters) {
+ const cluster = NumCast(doc?.layout_cluster);
+ if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
if (this._clusterSets.length <= cluster) {
setTimeout(() => doc && this.updateCluster(doc));
} else {
@@ -494,6 +583,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;
};
@@ -501,7 +594,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
trySelectCluster = (addToSel: boolean) => {
if (this._hitCluster !== -1) {
!addToSel && SelectionManager.DeselectAll();
- const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === this._hitCluster);
+ const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
this.selectDocuments(eles);
return true;
}
@@ -530,14 +623,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);
@@ -545,7 +635,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case InkTool.None:
if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
- setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, false);
+ setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
}
break;
}
@@ -591,7 +681,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
const inkDoc = Docs.Create.InkDocument(
ActiveInkColor(),
- Doc.ActiveTool,
ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
ActiveInkBezierApprox(),
ActiveFillColor(),
@@ -602,8 +691,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,
}
@@ -693,7 +782,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._batch?.end();
};
+ @action
onClick = (e: React.MouseEvent) => {
+ if (this._lightboxDoc) this._lightboxDoc = undefined;
if (this.onBrowseClickHandler()) {
if (this.props.DocumentView?.()) {
this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView(), clientX: e.clientX, clientY: e.clientY });
@@ -701,7 +792,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation();
e.preventDefault();
} else if (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3) {
- if (e.shiftKey) {
+ if (e.shiftKey && (this.props.renderDepth === 0 || this.isContentActive())) {
if (Date.now() - this._lastTap < 300) {
// reset zoom of freeform view to 1-to-1 on a shift + double click
this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
@@ -715,20 +806,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
scrollPan = (e: WheelEvent | { deltaX: number; deltaY: number }): void => {
+ 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
pan = (e: PointerEvent | React.Touch | { clientX: number; clientY: number }): void => {
+ 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;
};
+ _eraserLock = 0;
/**
* Erases strokes by intersecting them with an invisible "eraser stroke".
* By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments,
@@ -738,19 +832,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
const currPoint = { X: e.clientX, Y: e.clientY };
+ if (this._eraserLock) return false; // bcz: should be fixed by putting it on a queue to be processed after the last eraser movement is processed.
this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => {
if (!this._deleteList.includes(intersect.inkView)) {
this._deleteList.push(intersect.inkView);
- SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || '1');
+ SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.stroke_width?.toString()) || '1');
SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black');
// create a new curve by appending all curves of the current segment together in order to render a single new stroke.
if (!e.shiftKey) {
+ this._eraserLock++;
this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment =>
- GestureOverlay.Instance.dispatchGesture(
+ this.forceStrokeGesture(
+ e,
GestureUtils.Gestures.Stroke,
segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
)
);
+ setTimeout(() => this._eraserLock--);
}
// Lower ink opacity to give the user a visual indicator of deletion.
intersect.inkView.layoutDoc.opacity = 0.5;
@@ -759,17 +857,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
return false;
};
+ forceStrokeGesture = (e: PointerEvent, gesture: GestureUtils.Gestures, points: InkData, text?: any) => {
+ this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text));
+ };
@action
onPointerMove = (e: PointerEvent): boolean => {
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)) {
+ e.stopPropagation(); // we're moving a cluster, so stop propagation and return true to end panning and let the document drag take over
return true;
- } else this.pan(e);
+ }
+ // pan the view if this is a regular collection, or it's an overlay and the overlay is zoomed (otherwise, there's nothing to pan)
+ if (!this.props.isAnnotationOverlay || 1 - NumCast(this.rootDoc._freeform_scale_min, 1) / this.getLocalTransform().inverse().Scale) {
+ this.pan(e);
+ e.stopPropagation(); // if we are actually panning, stop propagation -- this will preven things like the overlayView from dragging the document while we're panning
+ }
}
return false;
};
@@ -783,7 +889,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(
@@ -837,7 +943,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const docCurveTVal = t + Math.floor(i / 4);
if (excludeT < startSegmentT || excludeT > docCurveTVal) {
const localStartTVal = startSegmentT - Math.floor(i / 4);
- segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t));
+ t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t));
segment.length && segments.push(segment);
}
// start a new segment from the intersection t value
@@ -877,7 +983,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));
@@ -887,106 +993,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (ink?.Document === otherInk.props.Document && neighboringSegment) continue;
const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
+ const c0 = otherCurve.get(0);
+ const c1 = otherCurve.get(1);
+ const apt = curve.project(c0);
+ const bpt = curve.project(c1);
+ if (apt.d !== undefined && apt.d < 1 && apt.t !== undefined && !tVals.includes(apt.t)) {
+ tVals.push(apt.t);
+ }
this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => {
// Converting the Bezier.js Split type to a t-value number.
const t = +val.toString().split('/')[0];
if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical).
});
+ if (bpt.d !== undefined && bpt.d < 1 && bpt.t !== undefined && !tVals.includes(bpt.t)) {
+ tVals.push(bpt.t);
+ }
}
});
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();
@@ -1002,51 +1028,42 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (deltaScale * invTransform.Scale > 20) {
deltaScale = 20 / invTransform.Scale;
}
- if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) {
- deltaScale = NumCast(this.rootDoc._viewScaleMin, 1) / invTransform.Scale;
+ if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) {
+ this.setPan(0, 0);
+ return;
+ }
+ if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) {
+ deltaScale = NumCast(this.rootDoc._freeform_scale_min, 1) / invTransform.Scale;
}
- const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
+ const localTransform = invTransform.scaleAbout(deltaScale, x, y);
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, -localTransform.TranslateY / safeScale);
+ this.setPan(-localTransform.TranslateX / safeScale, (this.props.originTopLeft ? undefined : NumCast(this.props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
}
};
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
+ if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
+ PresBox.Instance?.pauseAutoPres();
+ if (this.layoutDoc._Transform || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
e.stopPropagation();
- e.preventDefault();
+ const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
+ const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling;
switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) {
case freeformScrollMode.Pan:
- // if shift is selected then zoom
- if (e.ctrlKey) {
- if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
- // things that can scroll vertically should do that instead of zooming
- } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
- !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?
- }
- // otherwise pan
- } else if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
- // things that can scroll vertically should do that instead of zooming
- } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
- const dx = -e.deltaX;
- const dy = -e.deltaY;
- if (e.shiftKey) {
- !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dy, deltaY: 0 });
- } else {
- !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dx, deltaY: dy });
- }
+ // if ctrl is selected then zoom
+ if (!e.ctrlKey && this.props.isContentActive(true)) {
+ this.scrollPan({ deltaX: -e.deltaX, deltaY: e.shiftKey ? 0 : -Math.max(-1, Math.min(1, e.deltaY)) });
+ break;
}
- break;
default:
case freeformScrollMode.Zoom:
- if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
- // things that can scroll vertically should do that instead of zooming
- } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
- !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?
+ if ((e.ctrlKey || !scrollable) && this.props.isContentActive(true)) {
+ this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+ e.preventDefault();
}
break;
}
@@ -1074,40 +1091,61 @@ 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;
- const scale = this.getLocalTransform().inverse().Scale;
- const minScale = NumCast(this.rootDoc._viewScaleMin, 1);
- const minPanX = NumCast(this.rootDoc._panXMin, 0);
- const minPanY = NumCast(this.rootDoc._panYMin, 0);
- const newPanX = Math.min(minPanX + (1 - minScale / scale) * NumCast(this.rootDoc._panXMax, this.nativeWidth), Math.max(minPanX, panX));
- const newPanY = Math.min(this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : minPanY + (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, this.nativeHeight), Math.max(minPanY, panY));
- !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX);
- !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY);
+ if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc) {
+ this.setPanZoomTransition(panTime);
+ const minScale = NumCast(this.rootDoc._freeform_scale_min, 1);
+ const scale = 1 - minScale / this.getLocalTransform().inverse().Scale;
+ const minPanX = NumCast(this.rootDoc._freeform_panX_min, 0);
+ const minPanY = NumCast(this.rootDoc._freeform_panY_min, 0);
+ const maxPanX = NumCast(this.rootDoc._freeform_panX_max, this.nativeWidth);
+ const newPanX = Math.min(minPanX + scale * maxPanX, Math.max(minPanX, panX));
+ const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this.props.PanelWidth() - this.props.PanelHeight()) * this.props.ScreenToLocalTransform().Scale) / minScale;
+ const nativeHeight = (this.props.PanelHeight() / this.props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
+ const maxScrollTop = this.nativeHeight / this.props.ScreenToLocalTransform().Scale - this.props.PanelHeight();
+ const maxPanY =
+ minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
+ scale * NumCast(this.rootDoc._panY_max, nativeHeight) +
+ (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
+ let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
+ if (false && NumCast(this.rootDoc.layout_scrollTop) && NumCast(this.rootDoc._freeform_scale, minScale) !== minScale) {
+ const relTop = NumCast(this.rootDoc.layout_scrollTop) / maxScrollTop;
+ this.rootDoc.layout_scrollTop = undefined;
+ newPanY = minPanY + relTop * (maxPanY - minPanY);
+ } else if (fitYscroll > 2 && this.rootDoc.layout_scrollTop === undefined && NumCast(this.rootDoc._freeform_scale, minScale) === minScale) {
+ const maxPanY = minPanY + fitYscroll;
+ const relTop = (panY - minPanY) / (maxPanY - minPanY);
+ setTimeout(() => (this.rootDoc.layout_scrollTop = relTop * maxScrollTop), 10);
+ newPanY = minPanY;
+ }
+ !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?._type_collection !== CollectionViewType.Freeform || collectionDoc._freeform_ !== 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;
}
@@ -1117,117 +1155,61 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
bringToFront = (doc: Doc, sendToBack?: boolean) => {
if (sendToBack) {
- doc.zIndex = 0;
- } else if (doc.isInkMask) {
+ const docs = this.childLayoutPairs.map(pair => pair.layout).slice();
+ docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+ let zfirst = docs.length ? NumCast(docs[0].zIndex) : 0;
+ doc.zIndex = zfirst - 1;
+ } else if (doc.stroke_isInkMask) {
doc.zIndex = 5000;
} 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.Document.scrollHeight || 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 ? 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,
@@ -1237,10 +1219,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const panelWidth = this.props.isAnnotationOverlay ? this.nativeWidth : this.props.PanelWidth();
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 pw = panelWidth / NumCast(this.layoutDoc._freeform_scale, 1);
+ const ph = panelHeight / NumCast(this.layoutDoc._freeform_scale, 1);
+ 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;
@@ -1252,8 +1234,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),
};
};
@@ -1263,17 +1245,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
const docView = fieldProps.DocumentView?.();
- if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._singleLine) && ['Tab', 'Enter'].includes(e.key)) {
+ if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
e.stopPropagation?.();
const below = !e.altKey && e.key !== 'Tab';
- const layoutKey = StrCast(docView.LayoutFieldKey);
+ const layout_fieldKey = StrCast(docView.LayoutFieldKey);
const newDoc = Doc.MakeCopy(docView.rootDoc, true);
const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10;
else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10;
- if (layoutKey !== 'layout' && docView.rootDoc[layoutKey] instanceof Doc) {
- newDoc[layoutKey] = docView.rootDoc[layoutKey];
+ if (layout_fieldKey !== 'layout' && docView.rootDoc[layout_fieldKey] instanceof Doc) {
+ newDoc[layout_fieldKey] = docView.rootDoc[layout_fieldKey];
}
Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
@@ -1282,8 +1264,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 ? '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) {
@@ -1298,12 +1281,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}
@@ -1315,8 +1297,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
- isContentActive={emptyFunction}
- focus={this.focusDocument}
+ isContentActive={this.props.childContentsActive ?? emptyFunction}
+ focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
addDocTab={this.addDocTab}
addDocument={this.props.addDocument}
removeDocument={this.props.removeDocument}
@@ -1330,23 +1312,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
sizeProvider={this.childSizeProvider}
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
bringToFront={this.bringToFront}
- showTitle={this.props.childShowTitle}
- dontScaleFilter={this.props.dontScaleFilter}
+ layout_showTitle={this.props.childlayout_showTitle}
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);
+ const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.embedContainer);
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];
@@ -1357,32 +1339,43 @@ 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) {
+ this._lightboxDoc = doc;
+ return true;
+ }
+ if (this.childDocList?.includes(doc)) {
+ if (doc.hidden) doc.hidden = false;
return true;
}
}
return this.props.addDocTab(doc, where);
});
+ @observable _lightboxDoc: Opt<Doc>;
getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
- const curFrame = Cast(this.Document._currentFrame, 'number');
const childDoc = params.pair.layout;
const childDocLayout = Doc.Layout(childDoc);
+ const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+ 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 } = curFrame === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame);
- const { x, y, opacity } = curFrame === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, curFrame);
+ const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
+ 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.props.styleProvider?.(childDoc, this.props, StyleProp.BackgroundColor),
- opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
+ backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor),
+ opacity: !_width ? 0 : 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: '',
@@ -1479,7 +1472,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const newPool = new Map<string, PoolData>();
// prettier-ignore
switch (this.layoutEngine) {
- case computePassLayout.name : return { newPool, computedElementData: this.doEngineLayout(newPool, computePassLayout) };
+ case computePassLayout.name : return { newPool, computedElementData: this.doEngineLayout(newPool, computePassLayout) };
case computeTimelineLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
case computePivotLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) };
@@ -1503,6 +1496,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
) {
@@ -1518,70 +1512,36 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const elements = computedElementData.slice();
Array.from(newPool.entries())
.filter(entry => this.isCurrent(entry[1].pair.layout))
- .forEach((entry, i) =>
+ .forEach((entry, i) => {
+ const childData: ViewDefBounds = this.childDataProvider(entry[1].pair.layout, entry[1].replica);
+ const childSize = this.childSizeProvider(entry[1].pair.layout, entry[1].replica);
elements.push({
ele: this.getChildDocView(entry[1]),
- bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica),
- inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
- })
- );
+ bounds: childData.opacity === 0 ? { ...childData, width: 0, height: 0 } : { ...childData, width: childSize.width, height: childSize.height },
+ inkMask: BoolCast(entry[1].pair.layout.stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
+ });
+ });
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]));
}
- this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
+ this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
return elements;
}
- @action
- setViewSpec = (anchor: Doc, preview: boolean) => {
- if (preview) {
- this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters);
- this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters);
- } else {
- if (anchor.docFilters) {
- this.layoutDoc._docFilters = new List<string>(StrListCast(anchor.docFilters));
- }
- if (anchor.docRangeFilters) {
- this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters));
- }
- }
- return 0;
- };
+ 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.CollectionConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presTransition: 500, annotationOn: this.rootDoc });
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, type_collection: true, filters: true } }, this.rootDoc);
- @action
- scrollFocus = (anchor: Doc, options: DocFocusOptions) => {
- const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
- return PresBox.restoreTargetDocView(
- this.props.DocumentView?.(), //
- { pinDocLayout: BoolCast(anchor.presPinDocLayout) },
- anchor,
- focusSpeed,
- {
- pannable: anchor.presPinData ? 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;
- }
- const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), presTransition: 500, annotationOn: this.rootDoc });
- PresBox.pinDocView(anchor, { pinData: { pannable: true } }, this.rootDoc);
- const proto = Doc.GetProto(anchor);
- proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType;
- proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List<string>([]);
if (addAsAnnotation) {
- if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) {
- Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor);
+ if (Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
+ Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
} else {
- this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]);
+ this.dataDoc[this.props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
}
}
return anchor;
@@ -1589,6 +1549,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
componentDidMount() {
+ this.props.setContentView?.(this);
super.componentDidMount?.();
this.props.setBrushViewer?.(this.brushView);
setTimeout(
@@ -1596,7 +1557,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));
}
@@ -1605,7 +1566,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],
@@ -1615,8 +1576,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;
}
@@ -1649,7 +1610,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;
@@ -1676,8 +1641,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
'',
(iconFile, nativeWidth, nativeHeight) => {
this.dataDoc.icon = new ImageField(iconFile);
- this.dataDoc['icon-nativeWidth'] = nativeWidth;
- this.dataDoc['icon-nativeHeight'] = nativeHeight;
+ this.dataDoc['icon_nativeWidth'] = nativeWidth;
+ this.dataDoc['icon_nativeHeight'] = nativeHeight;
}
);
@@ -1703,7 +1668,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) {
@@ -1734,8 +1699,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();
@@ -1759,8 +1724,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;
});
};
@@ -1775,20 +1740,25 @@ 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'}`,
- event: () => (this.Document._fitContentsToBox = !this.fitContentsToBox),
- 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' });
@@ -1802,7 +1772,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!Doc.noviceMode
? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? 'Hide' : 'Show') + ' Snap Lines', event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: 'compress-arrows-alt' })
: null;
- !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._useClusters), icon: 'braille' }) : null;
+ !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
!viewctrls && ContextMenu.Instance.addItem({ description: 'UI Controls...', subitems: viewCtrlItems, icon: 'eye' });
const options = ContextMenu.Instance.findByDescription('Options...');
@@ -1817,31 +1787,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) => {
@@ -1852,7 +1800,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 }));
}
}
};
@@ -1890,20 +1838,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
incrementalRender = action(() => {
if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
- const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
+ const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
const loadIncrement = 5;
- for (var i = 0; i < Math.min(unrendered.length, loadIncrement); i++) {
- this._renderCutoffData.set(unrendered[i][Id] + '', true);
+ for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
+ this._renderCutoffData.set(layout_unrendered[i][Id] + '', true);
}
}
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 (
@@ -1940,13 +1888,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
r?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
}}
style={{ opacity: this.props.dontRenderDocuments ? 0.7 : undefined }}>
- {this.layoutDoc._backgroundGridShow ? (
+ {this.layoutDoc._freeform_backgroundGrid ? (
<div>
<CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render whenn taking snapshot of a dashboard and the background grid is on!!?
PanelWidth={this.props.PanelWidth}
PanelHeight={this.props.PanelHeight}
panX={this.panX}
panY={this.panY}
+ nativeDimScaling={this.nativeDim}
zoomScaling={this.zoomScaling}
layoutDoc={this.layoutDoc}
isAnnotationOverlay={this.isAnnotationOverlay}
@@ -1963,7 +1912,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
zoomScaling={this.zoomScaling}
presPaths={this.showPresPaths}
presPinView={BoolCast(this.Document.presPinView)}
- transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)}
+ transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))}
viewDefDivClick={this.props.viewDefDivClick}>
{this.children}
</CollectionFreeFormViewPannableContents>
@@ -1979,8 +1928,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const nh = this.nativeHeight;
const hscale = nh ? this.props.PanelHeight() / nh : 1;
const wscale = nw ? this.props.PanelWidth() / nw : 1;
- return wscale < hscale || this.layoutDoc.fitWidth ? wscale : hscale;
+ return wscale < hscale || this.layoutDoc.layout_fitWidth ? wscale : hscale;
}
+ nativeDim = () => this.nativeDimScaling;
private groupDropDisposer?: DragManager.DragDropDisposer;
protected createGroupEventsTarget = (ele: HTMLDivElement) => {
@@ -2007,13 +1957,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
1000
);
};
-
+ lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30);
+ lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30);
+ lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15);
+ onPassiveWheel = (e: WheelEvent) => {
+ const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
+ const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling;
+ this.props.isSelected() && !scrollable && e.preventDefault();
+ };
+ _oldWheel: any;
render() {
TraceMobx();
return (
<div
- className={'collectionfreeformview-container'}
- ref={this.createDashEventsTarget}
+ className="collectionfreeformview-container"
+ ref={r => {
+ this.createDashEventsTarget(r);
+ this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+ this._oldWheel = r;
+ // prevent wheel events from passivly propagating up through containers
+ r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ }}
onWheel={this.onPointerWheel}
onClick={this.onClick}
onPointerDown={this.onPointerDown}
@@ -2022,46 +1986,71 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onDragOver={e => e.preventDefault()}
onContextMenu={this.onContextMenu}
style={{
- pointerEvents:
- this.props.Document.type === DocumentType.MARKER
- ? 'none' // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach.
- : SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())
- ? 'all'
- : (this.props.pointerEvents?.() as any),
+ pointerEvents: SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement()) ? 'all' : (this.props.pointerEvents?.() as any),
+ textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
transform: `scale(${this.nativeDimScaling || 1})`,
width: `${100 / (this.nativeDimScaling || 1)}%`,
- height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.nativeDimScaling || 1)}%`, // : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+ height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
}}>
- {this._firstRender ? this.placeholder : this.marqueeView}
- {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
-
- {/* // uncomment to show snap lines */}
- <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
- <svg style={{ width: '100%', height: '100%' }}>
- {this._hLines?.map(l => (
- <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
- ))}
- {this._vLines?.map(l => (
- <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
- ))}
- </svg>
- </div>
+ {this._lightboxDoc ? (
+ <div style={{ padding: 15, width: '100%', height: '100%' }}>
+ <DocumentView
+ {...this.props}
+ Document={this._lightboxDoc}
+ DataDoc={undefined}
+ PanelWidth={this.lightboxPanelWidth}
+ PanelHeight={this.lightboxPanelHeight}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ onClick={this.onChildClickHandler}
+ onKey={this.onKeyDown}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ onBrowseClick={this.onBrowseClickHandler}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+ isContentActive={this.props.childContentsActive ?? emptyFunction}
+ addDocTab={this.addDocTab}
+ ScreenToLocalTransform={this.lightboxScreenToLocal}
+ fitContentsToBox={undefined}
+ focus={this.focus}
+ />
+ </div>
+ ) : (
+ <>
+ {this._firstRender ? this.placeholder : this.marqueeView}
+ {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+
+ {/* // uncomment to show snap lines */}
+ <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
+ <svg style={{ width: '100%', height: '100%' }}>
+ {this._hLines?.map(l => (
+ <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
+ ))}
+ {this._vLines?.map(l => (
+ <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
+ ))}
+ </svg>
+ </div>
- {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
- <div
- className="collectionFreeForm-groupDropper"
- ref={this.createGroupEventsTarget}
- style={{
- width: this.ChildDrag ? '10000' : '100%',
- height: this.ChildDrag ? '10000' : '100%',
- left: this.ChildDrag ? '-5000' : 0,
- top: this.ChildDrag ? '-5000' : 0,
- position: 'absolute',
- background: '#0009930',
- pointerEvents: 'all',
- }}
- />
- ) : null}
+ {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
+ <div
+ className="collectionFreeForm-groupDropper"
+ ref={this.createGroupEventsTarget}
+ style={{
+ width: this.ChildDrag ? '10000' : '100%',
+ height: this.ChildDrag ? '10000' : '100%',
+ left: this.ChildDrag ? '-5000' : 0,
+ top: this.ChildDrag ? '-5000' : 0,
+ position: 'absolute',
+ background: '#0009930',
+ pointerEvents: 'all',
+ }}
+ />
+ ) : null}
+ </>
+ )}
</div>
);
}
@@ -2085,7 +2074,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;
@@ -2179,7 +2169,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"
@@ -2207,6 +2197,7 @@ interface CollectionFreeFormViewBackgroundGridProps {
PanelWidth: () => number;
PanelHeight: () => number;
isAnnotationOverlay?: boolean;
+ nativeDimScaling: () => number;
zoomScaling: () => number;
layoutDoc: Doc;
cachedCenteringShiftX: number;
@@ -2224,10 +2215,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}
@@ -2262,20 +2253,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,
- 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) {
@@ -2289,9 +2284,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._freeform_panX = doc._freeform_panY = 0;
+ doc._freeform_scale = 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 0b7854926..641088675 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';
@@ -40,16 +39,7 @@ interface MarqueeViewProps {
nudge?: (x: number, y: number, nudgeTime?: number) => boolean;
ungroup?: () => void;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void;
- slowLoadDocuments: (
- files: File[] | string,
- options: DocumentOptions,
- generatedDocuments: Doc[],
- text: string,
- completed: ((doc: Doc[]) => void) | undefined,
- clientX: number,
- clientY: number,
- addDocument: (doc: Doc | Doc[]) => boolean
- ) => Promise<void>;
+ slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
}
export interface MarqueeViewBounds {
@@ -62,8 +52,8 @@ export interface MarqueeViewBounds {
@observer
export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps> {
public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) {
- const ps = NumCast(pinDoc._viewScale, 1);
- return { left: NumCast(pinDoc._panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps };
+ const ps = NumCast(pinDoc._freeform_scale, 1);
+ return { left: NumCast(pinDoc._freeform_panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._freeform_panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps };
}
private _commandExecuted = false;
@@ -112,7 +102,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._lassoPts = [];
};
- @undoBatch
@action
onKeyPress = (e: KeyboardEvent) => {
//make textbox and add it to this collection
@@ -120,8 +109,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const cm = ContextMenu.Instance;
const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
if (e.key === '?') {
- cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', useCors: true }), OpenWhere.addRight));
-
+ cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
cm.displayMenu(this._downX, this._downY, undefined, true);
e.stopPropagation();
} else if (e.key === 'u' && this.props.ungroup) {
@@ -167,7 +155,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);
});
})
@@ -183,7 +171,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
e.stopPropagation();
} else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) {
FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : '';
- FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('live text batch');
+ FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
this.props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this.props.xPadding === 0));
e.stopPropagation();
}
@@ -206,7 +194,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const rowProto = new Doc();
rowProto.title = rowProto.Id;
rowProto._width = 200;
- rowProto.isPrototype = true;
+ rowProto.isDataDoc = true;
for (let i = 1; i < ns.length - 1; i++) {
const values = ns[i].split('\t');
if (values.length === 1 && columns.length > 1) {
@@ -214,7 +202,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
continue;
}
const docDataProto = Doc.MakeDelegate(rowProto);
- docDataProto.isPrototype = true;
+ docDataProto.isDataDoc = true;
columns.forEach((col, i) => (docDataProto[columns[i]] = values.length > i ? (values[i].indexOf(Number(values[i]).toString()) !== -1 ? Number(values[i]) : values[i]) : undefined));
if (groupAttr) {
docDataProto._group = groupAttr;
@@ -238,6 +226,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 +335,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) {
@@ -370,10 +360,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@undoBatch
@action
- delete = () => {
+ delete = (e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
const selected = this.marqueeSelect(false);
SelectionManager.DeselectAll();
- selected.forEach(doc => this.props.removeDocument?.(doc));
+ selected.forEach(doc => (hide ? (doc.hidden = true) : this.props.removeDocument?.(doc)));
this.cleanupInteractions(false);
MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -387,21 +377,24 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
Doc.GetProto(doc).data = new List<Doc>(selected);
Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform';
!this.props.isAnnotationOverlay && Doc.AddFileOrphan(Doc.GetProto(doc));
- doc._panX = doc._panY = 0;
+ doc._freeform_panX = doc._freeform_panY = 0;
return doc;
})(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
- newCollection.system = undefined;
+ newCollection.isSystem = undefined;
newCollection._width = this.Bounds.width;
newCollection._height = this.Bounds.height;
newCollection._isGroup = makeGroup;
newCollection.forceActive = makeGroup;
+ newCollection.enableDragWhenActive = makeGroup;
newCollection.x = this.Bounds.left;
newCollection.y = this.Bounds.top;
- selected.forEach(d => (d.context = newCollection));
+ newCollection.layout_fitWidth = true;
+ selected.forEach(d => (d.embedContainer = newCollection));
this.hideMarquee();
return newCollection;
});
+ @undoBatch
@action
pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false);
@@ -422,25 +415,24 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
*/
@undoBatch
@action
- pinWithView = async () => {
- const doc = this.props.Document;
- TabDocView.PinDoc(doc, { pinViewport: this.Bounds });
+ pinWithView = () => {
+ TabDocView.PinDoc(this.props.Document, { pinViewport: this.Bounds });
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
};
@undoBatch
@action
- collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => {
- const selected = this.marqueeSelect(false);
+ collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => {
+ const selected = selection ?? this.marqueeSelect(false);
const activeFrame = selected.reduce((v, d) => v ?? Cast(d._activeFrame, 'number', null), undefined as number | undefined);
if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) {
this.props.removeDocument?.(selected);
}
const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === 't' ? Docs.Create.StackingDocument : undefined, group);
- newCollection._panX = this.Bounds.left + this.Bounds.width / 2;
- newCollection._panY = this.Bounds.top + this.Bounds.height / 2;
+ newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2;
+ newCollection._freeform_panY = this.Bounds.top + this.Bounds.height / 2;
newCollection._currentFrame = activeFrame;
this.props.addDocument?.(newCollection);
this.props.selectDocuments([newCollection]);
@@ -515,46 +507,46 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
};
@undoBatch
- @action
- summary = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false).map(d => {
this.props.removeDocument?.(d);
d.x = NumCast(d.x) - this.Bounds.left;
d.y = NumCast(d.y) - this.Bounds.top;
return d;
});
- 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 summary = Docs.Create.TextDocument('', {
+ backgroundColor: '#e2ad32',
+ x: this.Bounds.left,
+ y: this.Bounds.top,
+ followLinkToggle: true,
+ _width: 200,
+ _height: 200,
+ _layoutFitContentsToBox: true,
+ _layout_showSidebar: true,
+ title: 'overview',
+ });
const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
- DocUtils.MakeLink({ doc: summary }, { doc: portal }, 'summary of:summarized by', '');
+ DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });
portal.hidden = true;
this.props.addDocument?.(portal);
this.props.addLiveTextDocument(summary);
MarqueeOptionsMenu.Instance.fadeOut(true);
- };
+ });
@action
- background = (e: KeyboardEvent | React.PointerEvent | undefined) => {
- const newCollection = this.getCollection([], undefined, undefined);
- this.props.addDocument?.(newCollection);
- MarqueeOptionsMenu.Instance.fadeOut(true);
- this.hideMarquee();
- setTimeout(() => this.props.selectDocuments([newCollection]));
- };
-
- @undoBatch
- marqueeCommand = action((e: KeyboardEvent) => {
+ marqueeCommand = (e: KeyboardEvent) => {
if (this._commandExecuted || (e as any).propagationIsStopped) {
return;
}
- if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd') {
+ if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd' || e.key === 'h') {
this._commandExecuted = true;
e.stopPropagation();
(e as any).propagationIsStopped = true;
- this.delete();
+ this.delete(e, e.key === 'h');
e.stopPropagation();
}
- if ('cbtsSpg'.indexOf(e.key) !== -1) {
+ if ('ctsSpg'.indexOf(e.key) !== -1) {
this._commandExecuted = true;
e.stopPropagation();
e.preventDefault();
@@ -562,7 +554,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (e.key === 'g') this.collection(e, true);
if (e.key === 'c' || e.key === 't') this.collection(e);
if (e.key === 's' || e.key === 'S') this.summary(e);
- if (e.key === 'b') this.background(e);
if (e.key === 'p') this.pileup(e);
this.cleanupInteractions(false);
}
@@ -572,7 +563,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
e.preventDefault();
this._lassoFreehand = !this._lassoFreehand;
}
- });
+ };
touchesLine(r1: { left: number; top: number; width: number; height: number }) {
for (const lassoPt of this._lassoPts) {
@@ -609,7 +600,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..3561844da 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,11 +11,11 @@ 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';
import { StyleProp } from '../../StyleProvider';
+import { UndoStack } from '../../UndoStack';
import { CollectionStackedTimeline } from '../CollectionStackedTimeline';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionLinearView.scss';
@@ -45,7 +45,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 +79,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,19 +150,29 @@ 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>
);
};
+
getDisplayDoc = (doc: Doc, preview: boolean = false) => {
- if (doc.icon === 'linkui') return this.getLinkUI();
- if (doc.icon === 'currentlyplayingui') return this.getCurrentlyPlayingUI();
+ // hack to avoid overhead of making UndoStack,etc into DocumentView style Boxes. If the UndoStack is ever intended to become part of the persisten state of the dashboard, then this would have to change.
+ // prettier-ignore
+ switch (doc.layout) {
+ case '<LinkingUI>': return this.getLinkUI();
+ case '<CurrentlyPlayingUI>': return this.getCurrentlyPlayingUI();
+ case '<UndoStack>': return <UndoStack key={doc[Id]} width={200} height={40} inline={true} />;
+ }
- const nested = doc._viewType === CollectionViewType.Linear;
+ const nested = doc._type_collection === CollectionViewType.Linear;
const hidden = doc.hidden === true;
let dref: Opt<HTMLDivElement>;
@@ -175,10 +185,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 +204,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 +216,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 +239,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,22 +256,23 @@ 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;
})}
/>
- <div
- className="collectionLinearView-content"
- style={{
- height: this.dimension(),
- flexDirection: flexDir as any,
- gap: flexGap,
- }}>
- {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
- </div>
+ {!this.layoutDoc.linearViewIsExpanded ? null : (
+ <div
+ className="collectionLinearView-content"
+ style={{
+ height: this.dimension(),
+ flexDirection: flexDir as any,
+ gap: flexGap,
+ }}>
+ {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
+ </div>
+ )}
</div>
</div>
);
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..3a0c2c85c 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -1,599 +1,239 @@
@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;
-.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-table {
+ background-color: $white;
+ cursor: grab;
-.documentView-node-topmost {
- text-align: left;
- transform-origin: center top;
- display: inline-block;
-}
+ .schema-table-content {
+ overflow: overlay;
+ scroll-behavior: smooth;
+ }
-.collectionSchema-col {
- height: 100%;
-}
+ .schema-column-menu,
+ .schema-filter-menu {
+ background: $light-gray;
+ position: absolute;
+ min-width: 200px;
+ max-width: 400px;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ z-index: 1;
-.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-input {
+ width: calc(100% - 20px);
+ margin: 10px;
+ }
-.collectionSchemaView-header {
- height: 100%;
- color: gray;
- z-index: 100;
- overflow-y: visible;
- display: flex;
- justify-content: space-between;
- flex-wrap: wrap;
-}
+ .schema-search-result {
+ cursor: pointer;
+ padding: 5px 10px;
+ width: 100%;
-button.add-column {
- width: 28px;
-}
+ &:hover {
+ background-color: $medium-gray;
+ }
-.collectionSchemaView-menuOptions-wrapper {
- background: rgb(241, 239, 235);
- display: flex;
- cursor: default;
- height: 100%;
- align-content: center;
- align-items: center;
-}
+ .schema-search-result-type,
+ .schema-search-result-desc {
+ color: $dark-gray;
+ font-size: $body-text;
+ }
+ }
-.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;
- }
- }
+ .schema-key-search,
+ .schema-new-key-options {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ }
- .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-new-key-options {
+ margin: 10px;
+ .schema-key-warning {
+ color: red;
+ font-weight: normal;
+ align-self: center;
+ }
}
- }
- }
- .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-key-list {
+ width: 100%;
+ max-height: 300px;
+ overflow-y: auto;
}
- }
- }
-}
-.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;
-}
+ .schema-key-type-option {
+ margin: 2px 0px;
-.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;
- }
- }
-}
+ input {
+ margin-right: 5px;
+ }
+ }
-.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 {
- 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;
+ .schema-key-default-val {
+ margin: 5px 0;
+ }
+
+ .schema-column-menu-button {
+ cursor: pointer;
+ padding: 2px 5px;
+ background: $medium-blue;
+ border-radius: 9999px;
+ color: $white;
+ width: fit-content;
+ margin: 5px;
+ align-self: center;
}
}
}
- .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;
- }
+
+ .schema-preview-divider {
+ height: 100%;
+ background: black;
+ cursor: ew-resize;
}
}
-.collectionSchemaView-cellContainer {
- width: 100%;
- height: unset;
-}
+.schema-header-row {
+ cursor: grab;
+ justify-content: flex-end;
-.collectionSchemaView-cellContents {
- width: 100%;
+ .row-menu {
+ display: flex;
+ justify-content: flex-end;
+ }
}
-.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-x: hidden;
+ overflow-y: auto;
+ padding: 5px;
+ display: inline-block;
}
-.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..36abad87e 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,646 +1,974 @@
import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked } from 'mobx';
+import { action, computed, observable, ObservableMap, observe, trace } 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, DocListCast, Field, NumListCast, 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, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
+import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Documents';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { DragManager } from '../../../util/DragManager';
import { SelectionManager } from '../../../util/SelectionManager';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { Transform } from '../../../util/Transform';
-import { undoBatch } from '../../../util/UndoManager';
+import { undoable, 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 { Colors } from '../../global/globalEnums';
+import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+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,
+ Image,
+ RTF,
+ Any,
}
-// 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 const FInfotoColType: { [key: string]: ColumnType } = {
+ string: ColumnType.String,
+ number: ColumnType.Number,
+ boolean: ColumnType.Boolean,
+ date: ColumnType.Date,
+ image: ColumnType.Image,
+ rtf: ColumnType.RTF,
+};
+
+const defaultColumnKeys: string[] = ['title', 'type', 'author', 'author_date', '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 _keysDisposer: any;
+ private _closestDropIndex: number = 0;
+ private _previewRef: HTMLDivElement | null = null;
+ private _makeNewColumn: boolean = false;
+ private _documentOptions: DocumentOptions = new DocumentOptions();
+ private _tableContentRef: HTMLDivElement | null = null;
+
+ static _rowHeight: number = 50;
+ static _rowSingleLineHeight: number = 32;
+ public static _minColWidth: number = 25;
+ public static _rowMenuWidth: number = 60;
+ public static _previewDividerWidth: number = 4;
+ public static _newNodeInputHeight: number = 30;
+ public fieldInfos = new ObservableMap<string, FInfo>();
+
+ @observable _menuKeys: string[] = [];
+ @observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
+ @observable _colEles: HTMLDivElement[] = [];
+ @observable _displayColumnWidths: number[] | undefined;
+ @observable _columnMenuIndex: number | undefined;
+ @observable _newFieldWarning: string = '';
+ @observable _makeNewField: boolean = false;
+ @observable _newFieldDefault: any = 0;
+ @observable _newFieldType: ColumnType = ColumnType.Number;
+ @observable _menuValue: string = '';
+ @observable _filterColumnIndex: number | undefined;
+ @observable _filterSearchValue: string = '';
+ @observable _selectedCell: [Doc, number] | undefined;
+
+ @computed get _selectedDocs() {
+ return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.rootDoc));
+ }
- @computed get previewWidth() {
- return () => NumCast(this.props.Document.schemaPreviewWidth);
+ @computed get documentKeys() {
+ return Array.from(this.fieldInfos.keys());
}
- @computed get previewHeight() {
- return () => this.props.PanelHeight() - 2 * this.borderWidth;
+
+ @computed get previewWidth() {
+ return NumCast(this.layoutDoc.schema_previewWidth);
}
+
@computed get tableWidth() {
- return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth();
+ return this.props.PanelWidth() - this.previewWidth - (this.previewWidth === 0 ? 0 : CollectionSchemaView._previewDividerWidth);
}
- @computed get borderWidth() {
- return Number(COLLECTION_BORDER_WIDTH);
+
+ @computed get columnKeys() {
+ return Cast(this.layoutDoc.schema_columnKeys, listSpec('string'), defaultColumnKeys);
}
- @computed get scale() {
- return this.props.ScreenToLocalTransform().Scale;
+
+ @computed get storedColumnWidths() {
+ const widths = NumListCast(
+ this.layoutDoc.schema_columnWidths,
+ 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) {
+ return widths.map(w => (w / totalWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth));
+ }
+ return widths;
}
- @computed get columns() {
- return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
+
+ @computed get displayColumnWidths() {
+ return this._displayColumnWidths ?? this.storedColumnWidths;
}
- set columns(columns: SchemaHeaderField[]) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns);
+
+ @computed get sortField() {
+ return StrCast(this.layoutDoc.sortField);
}
- @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 sortDesc() {
+ return BoolCast(this.layoutDoc.sortDesc);
}
- get documentKeys() {
- const docs = this.childDocs;
- const keys: { [key: string]: boolean } = {};
- // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
- // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
- // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
- // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
- // is displayed (unlikely) it won't show up until something else changes.
- //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));
- return Array.from(Object.keys(keys));
+ @action
+ componentDidMount() {
+ this.props.setContentView?.(this);
+ document.addEventListener('keydown', this.onKeyDown);
+
+ Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1]));
+ this._keysDisposer = observe(
+ this.rootDoc[this.fieldKey ?? 'data'] as List<Doc>,
+ change => {
+ switch (change.type as any) {
+ case 'splice':
+ // prettier-ignore
+ (change as any).added.forEach((doc: Doc) => // for each document added
+ Doc.GetAllPrototypes(doc.value as Doc).forEach(proto => // for all of its prototypes (and itself)
+ Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them
+ !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo(''))))));
+ break;
+ case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
+ }
+ },
+ true
+ );
}
- @action setHeaderIsEditing = (isEditing: boolean) => (this._headerIsEditing = isEditing);
+ componentWillUnmount() {
+ this._keysDisposer?.();
+ document.removeEventListener('keydown', this.onKeyDown);
+ }
- @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;
- }
- });
+ rowIndex = (doc: Doc) => this.sortedDocs.docs.indexOf(doc);
- @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
+ @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.sortedDocs.docs[lastIndex];
+ if (lastIndex >= 0 && lastIndex < this.childDocs.length - 1) {
+ !e.shiftKey && this.clearSelection();
+ const newDoc = this.sortedDocs.docs[lastIndex + 1];
+ if (this._selectedDocs.includes(newDoc)) {
+ SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+ } else {
+ this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
+ this._selectedCell && (this._selectedCell[0] = newDoc);
+ this.scrollToDoc(newDoc, {});
+ }
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ break;
+ case 'ArrowUp':
+ {
+ const firstDoc = this._selectedDocs.lastElement();
+ const firstIndex = this.rowIndex(firstDoc);
+ const curDoc = this.sortedDocs.docs[firstIndex];
+ if (firstIndex > 0 && firstIndex < this.childDocs.length) {
+ !e.shiftKey && this.clearSelection();
+ const newDoc = this.sortedDocs.docs[firstIndex - 1];
+ if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+ else {
+ this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
+ this._selectedCell && (this._selectedCell[0] = newDoc);
+ this.scrollToDoc(newDoc, {});
+ }
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ break;
+ case 'ArrowRight':
+ if (this._selectedCell) {
+ this._selectedCell[1] = Math.min(this._selectedCell[1] + 1, this.columnKeys.length - 1);
+ } else if (this._selectedDocs.length > 0) {
+ this.selectCell(this._selectedDocs[0], 0);
+ }
+ break;
+ case 'ArrowLeft':
+ if (this._selectedCell) {
+ this._selectedCell[1] = Math.max(this._selectedCell[1] - 1, 0);
+ } else if (this._selectedDocs.length > 0) {
+ this.selectCell(this._selectedDocs[0], 0);
+ }
+ break;
+ case 'Backspace': {
+ this.removeDocument(this._selectedDocs);
+ break;
+ }
+ case 'Escape': {
+ this.deselectCell();
+ }
+ }
}
};
@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;
+ setColumnSort = (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);
+ 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.schema_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.schema_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.schema_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.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)));
+
+ let currKeys = this.columnKeys.slice();
+ currKeys.splice(index, 1);
+ this.layoutDoc.schema_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;
- 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 (index !== 0) {
+ growing = change < 0 ? index : index - 1;
+ shrinking = change < 0 ? index - 1 : index;
+ }
- 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>
- );
- };
+ if (shrinking === undefined || growing === undefined) 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>
- );
- };
+ change = Math.abs(change);
+ if (this._displayColumnWidths[shrinking] - change < CollectionSchemaView._minColWidth) {
+ change = this._displayColumnWidths[shrinking] - CollectionSchemaView._minColWidth;
+ }
- renderColors = (col: any) => {
- const selected = col.color;
+ this._displayColumnWidths[shrinking] -= change * this.props.ScreenToLocalTransform().Scale;
+ this._displayColumnWidths[growing] += change * this.props.ScreenToLocalTransform().Scale;
- 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 false;
+ }
+ return true;
+ };
- 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>
- );
+ @action
+ finishResize = () => {
+ this.layoutDoc.schema_columnWidths = new List<number>(this._displayColumnWidths);
+ this._displayColumnWidths = undefined;
};
@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')]);
- } 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;
- }
- }
- }
- }
+ moveColumn = (fromIndex: number, toIndex: number) => {
+ let currKeys = this.columnKeys.slice();
+ currKeys.splice(toIndex, 0, currKeys.splice(fromIndex, 1)[0]);
+ this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
+
+ let currWidths = this.storedColumnWidths.slice();
+ currWidths.splice(toIndex, 0, currWidths.splice(fromIndex, 1)[0]);
+ this.layoutDoc.schema_columnWidths = new List<number>(currWidths);
};
@action
- openHeader = (col: any, screenx: number, screeny: number) => {
- this._col = col;
- this._headerOpen = true;
- this._pointerX = screenx;
- this._pointerY = screeny;
+ 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);
+
+ document.removeEventListener('pointermove', this.highlightDropColumn);
+ document.addEventListener('pointermove', this.highlightDropColumn);
+ let stopHighlight = (e: PointerEvent) => {
+ document.removeEventListener('pointermove', this.highlightDropColumn);
+ document.removeEventListener('pointerup', stopHighlight);
+ };
+ document.addEventListener('pointerup', stopHighlight);
+
+ return true;
};
@action
- closeHeader = () => {
- this._headerOpen = false;
+ highlightDropColumn = (e: PointerEvent) => {
+ e.stopPropagation();
+ const mouseX = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
+ let index: number | undefined;
+ this.displayColumnWidths.reduce((total, curr, i) => {
+ if (total <= mouseX && total + curr >= mouseX) {
+ if (mouseX <= total + curr / 2) index = i;
+ else index = i + 1;
+ }
+ return total + curr;
+ }, CollectionSchemaView._rowMenuWidth);
+
+ this._colEles.forEach((colRef, i) => {
+ let leftStyle = '';
+ let rightStyle = '';
+ if (i + 1 === index) rightStyle = `solid 2px ${Colors.MEDIUM_BLUE}`;
+ if (i === index && i === 0) leftStyle = `solid 2px ${Colors.MEDIUM_BLUE}`;
+ colRef.style.borderLeft = leftStyle;
+ colRef.style.borderRight = rightStyle;
+ this.childDocs.forEach(doc => {
+ this._rowEles.get(doc).children[1].children[i].style.borderLeft = leftStyle;
+ this._rowEles.get(doc).children[1].children[i].style.borderRight = rightStyle;
+ });
+ });
};
- @undoBatch
@action
- deleteColumn = (key: string) => {
- const columns = this.columns;
- if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([]);
+ 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 {
- const index = columns.map(c => c.heading).indexOf(key);
- if (index > -1) {
- columns.splice(index, 1);
- this.columns = columns;
- }
+ this._colEles[index] = ref;
}
- this.closeHeader();
};
- getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(-this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, -this.borderWidth);
+ @action
+ addDocToSelection = (doc: Doc, extendSelection: boolean, index: number) => {
+ const rowDocView = DocumentManager.Instance.getDocumentView(doc);
+ if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection);
};
@action
- onHeaderClick = (e: React.PointerEvent) => {
- e.stopPropagation();
+ clearSelection = () => SelectionManager.DeselectAll();
+
+ selectRows = (rootDoc: Doc, lastSelected: Doc) => {
+ const index = this.rowIndex(rootDoc);
+ const lastSelectedRow = this.rowIndex(lastSelected);
+ const startRow = Math.min(lastSelectedRow, index);
+ const endRow = Math.max(lastSelectedRow, index);
+ for (let i = startRow; i <= endRow; i++) {
+ const currDoc = this.sortedDocs.docs[i];
+ if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true, i);
+ }
};
@action
- onWheel(e: React.WheelEvent) {
- const scale = this.props.ScreenToLocalTransform().Scale;
- this.props.isContentActive(true) && e.stopPropagation();
- }
+ selectCell = (doc: Doc, index: number) => (this._selectedCell = [doc, index]);
- @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>
- );
- }
-
- private createTarget = (ele: HTMLDivElement) => {
- this._previewCont = ele;
- super.CreateDropTarget(ele);
- };
+ @action
+ deselectCell = () => (this._selectedCell = undefined);
- isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable;
+ sortedSelectedDocs = () => this.sortedDocs.docs.filter(doc => this._selectedDocs.includes(doc));
- @action setFocused = (doc: Doc) => (this._focusedTable = doc);
+ setDropIndex = (index: number) => (this._closestDropIndex = index);
- @action setPreviewDoc = (doc: Opt<Doc>) => {
- SelectionManager.SelectSchemaViewDoc(doc);
- this._previewDoc = doc;
+ @action
+ onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.complete.columnDragData) {
+ e.stopPropagation();
+ const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
+ let index = de.complete.columnDragData.colIndex;
+ this.displayColumnWidths.reduce((total, curr, i) => {
+ if (total <= mouseX && total + curr >= mouseX) {
+ if (mouseX <= total + curr / 2) index = i;
+ else index = i + 1;
+ }
+ return total + curr;
+ }, CollectionSchemaView._rowMenuWidth);
+ this.moveColumn(de.complete.columnDragData.colIndex, index);
+
+ this._colEles.forEach((colRef, i) => {
+ colRef.style.borderLeft = '';
+ colRef.style.borderRight = '';
+ this.childDocs.forEach(doc => {
+ this._rowEles.get(doc).children[1].children[i].style.borderLeft = '';
+ this._rowEles.get(doc).children[1].children[i].style.borderRight = '';
+ });
+ });
+
+ return true;
+ }
+ const draggedDocs = de.complete.docDragData?.draggedDocuments;
+ if (draggedDocs && super.onInternalDrop(e, de) && !this.sortField) {
+ 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.clearSelection();
+ 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;
};
- //toggles preview side-panel of schema
@action
- toggleExpander = () => {
- this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0;
+ onExternalDrop = async (e: React.DragEvent): Promise<void> => {
+ super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc)))));
};
- onDividerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander);
- };
+ onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction);
+
@action
onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
- const nativeWidth = this._previewCont!.getBoundingClientRect();
+ 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.props.Document.schemaPreviewWidth = width;
+ this.layoutDoc.schema_previewWidth = width;
return false;
};
- 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);
- }
- };
+ menuCallback = (x: number, y: number) => {
+ ContextMenu.Instance.clearItems();
- @computed
- get previewDocument(): Doc | undefined {
- return this._previewDoc;
- }
+ DocUtils.addDocumentCreatorMenuItems(this.addRow, this.addRow, x, y, true);
- @computed
- get dividerDragger() {
- return this.previewWidth() === 0 ? null : (
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown}>
- <div className="collectionSchemaView-dividerDragger" />
- </div>
- );
- }
+ ContextMenu.Instance.displayMenu(x, y, undefined, true);
+ };
- @computed
- get previewPanel() {
- 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>
- );
- }
+ focusDocument = (doc: Doc, options: DocFocusOptions) => {
+ Doc.BrushDoc(doc);
+ this.scrollToDoc(doc, options);
+ return undefined;
+ };
- @computed
- get schemaTable() {
- 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}
- />
- );
- }
+ scrollToDoc = (doc: Doc, options: DocFocusOptions) => {
+ const found = this._tableContentRef && Array.from(this._tableContentRef.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
+ if (found) {
+ const rect = found.getBoundingClientRect();
+ const localRect = this.props.ScreenToLocalTransform().transformBounds(rect.left, rect.top, rect.width, rect.height);
+ if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this.props.PanelHeight()) {
+ let focusSpeed = options.zoomTime ?? 50;
+ smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - this.rowHeightFunc(), options.easeFunc);
+ return focusSpeed;
+ }
+ }
+ };
- @computed
- public get schemaToolbar() {
- 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>
- </div>
- );
+ @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))} />;
+ }
}
- 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();
+ onSearchKeyDown = (e: React.KeyboardEvent) => {
+ switch (e.key) {
+ case 'Enter':
+ this._menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuKeys[0]) : action(() => (this._makeNewField = true))();
+ break;
+ case 'Escape':
+ this.closeColumnMenu();
+ break;
}
};
@action
- onTableClick = (e: React.MouseEvent): void => {
- if (!(e.target as any)?.className?.includes?.('collectionSchemaView-cell') && !(e.target instanceof HTMLSpanElement)) {
- this.setPreviewDoc(undefined);
+ setKey = (key: string, defaultVal?: any) => {
+ if (this._makeNewColumn) {
+ this.addColumn(key, defaultVal);
} else {
- e.stopPropagation();
+ this.changeColumnKey(this._columnMenuIndex!, key, defaultVal);
}
- this.setFocused(this.props.Document);
- this.closeHeader();
+ this.closeColumnMenu();
+ };
+
+ setColumnValues = (key: string, value: string) => {
+ let success: boolean = true;
+ this.childDocs.forEach(doc => success && KeyValueBox.SetField(doc, key, value));
+ return success;
+ };
+
+ @action
+ openColumnMenu = (index: number, newCol: boolean) => {
+ this._makeNewColumn = false;
+ this._columnMenuIndex = index;
+ this._menuValue = '';
+ this._menuKeys = 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._filterSearchValue = '';
};
- 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;
+ @action
+ closeFilterMenu = () => {
+ this._filterColumnIndex = undefined;
+ };
+
+ openContextMenu = (x: number, y: number, index: number) => {
+ this.closeColumnMenu();
+ this.closeFilterMenu();
+ 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',
});
- this.columns = columns;
+ ContextMenu.Instance.addItem({
+ description: 'Delete column',
+ event: () => this.removeColumn(index),
+ icon: 'trash',
+ });
+ ContextMenu.Instance.displayMenu(x, y, undefined, false);
};
@action
- setColumns = (columns: SchemaHeaderField[]) => (this.columns = columns);
+ updateKeySearch = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._menuValue = e.target.value;
+ this._menuKeys = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
+ };
- @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;
+ getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
- if (oldIndex === newIndex) return;
+ removeFieldFilters = (field: string) => {
+ this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove'));
+ };
- columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]);
- this.columns = columns;
+ onFilterKeyDown = (e: React.KeyboardEvent) => {
+ switch (e.key) {
+ case 'Enter':
+ case 'Escape':
+ this.closeFilterMenu();
+ break;
+ }
};
- onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation();
+ @action
+ updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterSearchValue = e.target.value);
- 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 newFieldMenu() {
+ return (
+ <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>
+ );
+ }
+
+ onPassiveWheel = (e: WheelEvent) => {
+ // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
+ if (!this._oldWheel.scrollTop && e.deltaY <= 0) e.preventDefault();
+ e.stopPropagation();
+ };
+ _oldWheel: any;
+ @computed get keysDropdown() {
+ return (
+ <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 => {
+ this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+ this._oldWheel = r;
+ r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ }}>
+ {this._menuKeys.map(key => (
+ <div
+ className="schema-search-result"
+ onPointerDown={e => {
+ e.stopPropagation();
+ this.setKey(key);
+ }}>
+ <p>
+ <span className="schema-search-result-key">
+ {key}
+ {this.fieldInfos.get(key)!.fieldType ? ', ' : ''}
+ </span>
+ <span className="schema-search-result-type" style={{ color: this.fieldInfos.get(key)!.readOnly ? 'red' : 'inherit' }}>
+ {this.fieldInfos.get(key)!.fieldType}
+ </span>
+ </p>
+ <p className="schema-search-result-desc">{this.fieldInfos.get(key)!.description}</p>
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+ }
+
+ @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="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>
+ );
+ }
+
+ @computed get renderFilterOptions() {
+ const keyOptions: string[] = [];
+ const columnKey = this.columnKeys[this._filterColumnIndex!];
+ const allDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
+ allDocs.forEach(doc => {
+ const value = StrCast(doc[columnKey]);
+ if (!keyOptions.includes(value) && value !== '' && (this._filterSearchValue === '' || value.includes(this._filterSearchValue))) {
+ keyOptions.push(value);
+ }
+ });
+
+ const filters = StrListCast(this.Document._docFilters);
+ return 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>
+ );
+ });
+ }
+
+ @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._filterSearchValue} 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();
})}>
- {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>}
- </Measure>
+ done
+ </div>
</div>
);
+ }
+
+ @computed get sortedDocs() {
+ trace();
+ 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 };
+ }
+ rowHeightFunc = () => (BoolCast(this.layoutDoc._schema_singleLine) ? CollectionSchemaView._rowSingleLineHeight : CollectionSchemaView._rowHeight);
+ 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: this.rowHeightFunc() }}>
+ <div className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth }}>
+ <div className="schema-header-button" onPointerDown={e => (this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true))}>
+ <FontAwesomeIcon icon="plus" />
+ </div>
+ </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.setColumnSort}
+ rowHeight={this.rowHeightFunc}
+ 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} rowHeight={this.rowHeightFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} />
+ <EditableView
+ GetValue={returnEmptyString}
+ SetValue={undoable(value => (value ? this.addRow(Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true })) : false), 'add text doc')}
+ placeholder={"Type ':' for commands"}
+ contents={'+ New Node'}
+ menuCallback={this.menuCallback}
+ height={CollectionSchemaView._newNodeInputHeight}
+ />
</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}
+ defaultDoubleClick={returnIgnore}
+ 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={returnFalse}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ />
+ )}
+ </div>
+ )}
+ </div>
+ );
+ }
+}
+
+interface CollectionSchemaViewDocsProps {
+ schema: CollectionSchemaView;
+ setRef: (ref: HTMLDivElement | null) => void;
+ childDocs: () => { docs: Doc[] };
+ rowHeight: () => number;
+}
+
+@observer
+class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsProps> {
+ tableWidthFunc = () => this.props.schema.tableWidth;
+ childScreenToLocal = computedFn((index: number) => () => this.props.schema.props.ScreenToLocalTransform().translate(0, -this.props.rowHeight() - index * this.props.rowHeight()));
+ render() {
+ return (
+ <div className="schema-table-content" ref={this.props.setRef} style={{ height: `calc(100% - ${CollectionSchemaView._newNodeInputHeight + this.props.rowHeight()}px)` }}>
+ {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={{ height: this.props.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.props.rowHeight}
+ styleProvider={DefaultStyleProvider}
+ waitForDoubleClickToClick={returnNever}
+ defaultDoubleClick={returnIgnore}
+ 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={this.props.schema.props.whenChildContentsActiveChanged}
+ hideDecorations={true}
+ hideTitle={true}
+ hideDocumentButtonBar={true}
+ hideLinkAnchors={true}
+ layout_fitWidth={returnTrue}
+ scriptContext={this}
+ canEmbedOnDrag={true}
+ />
+ </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..46c2f622b
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -0,0 +1,78 @@
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } 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 | undefined, desc?: boolean) => void;
+ removeColumn: (index: number) => void;
+ rowHeight: () => number;
+ 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> {
+ @observable _ref: HTMLDivElement | null = null;
+
+ @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.sortDesc) {
+ this.props.setSort(undefined);
+ } else if (this.props.sortField == this.fieldKey) {
+ this.props.setSort(this.fieldKey, true);
+ } 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 => {
+ if (col) {
+ this._ref = 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..978b65053
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -0,0 +1,152 @@
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import { computedFn } from 'mobx-utils';
+import { Doc } from '../../../../fields/Doc';
+import { BoolCast } from '../../../../fields/Types';
+import { DragManager } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { undoable, 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() {
+ return this.props.DocumentView?.().props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView;
+ }
+
+ @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.embedContainer === 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);
+ };
+
+ getFinfo = computedFn((fieldKey: string) => this.schemaView?.fieldInfos.get(fieldKey));
+ selectCell = (doc: Doc, col: number) => this.schemaView?.selectCell(doc, col);
+ deselectCell = () => this.schemaView?.deselectCell();
+ selectedCell = () => this.schemaView?._selectedCell;
+ setColumnValues = (field: any, value: any) => this.schemaView?.setColumnValues(field, value) ?? false;
+ columnWidth = computedFn((index: number) => () => this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth);
+ render() {
+ return (
+ <div
+ className="schema-row"
+ style={{ height: this.props.PanelHeight(), backgroundColor: this.props.isSelected() ? Colors.LIGHT_BLUE : 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={undoable(e => {
+ e.stopPropagation();
+ this.props.removeDocument?.(this.rootDoc);
+ }, 'Delete Row')}>
+ <FontAwesomeIcon icon="times" />
+ </div>
+ <div
+ className="schema-row-button"
+ onPointerDown={undoable(e => {
+ e.stopPropagation();
+ this.props.addDocTab(this.rootDoc, OpenWhere.addRight);
+ }, 'Open Doc on Right')}>
+ <FontAwesomeIcon icon="external-link-alt" />
+ </div>
+ </div>
+ <div className="row-cells">
+ {this.schemaView?.columnKeys?.map((key, index) => (
+ <SchemaTableCell
+ key={key}
+ Document={this.rootDoc}
+ col={index}
+ fieldKey={key}
+ allowCRs={false} // to enter text with new lines, must use \n
+ columnWidth={this.columnWidth(index)}
+ rowHeight={this.schemaView.rowHeightFunc}
+ isRowActive={this.props.isContentActive}
+ getFinfo={this.getFinfo}
+ selectCell={this.selectCell}
+ deselectCell={this.deselectCell}
+ selectedCell={this.selectedCell}
+ setColumnValues={this.setColumnValues}
+ oneLine={BoolCast(this.schemaDoc?._singleLine)}
+ />
+ ))}
+ </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..01bfbcddb
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -0,0 +1,313 @@
+import React = require('react');
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { extname } from 'path';
+import DatePicker from 'react-datepicker';
+import { DateField } from '../../../../fields/DateField';
+import { Doc, DocListCast, Field } from '../../../../fields/Doc';
+import { RichTextField } from '../../../../fields/RichTextField';
+import { BoolCast, Cast, DateCast, FieldValue } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils';
+import { FInfo } from '../../../documents/Documents';
+import { DocFocusOrOpen } from '../../../util/DocumentManager';
+import { dropActionType } from '../../../util/DragManager';
+import { Transform } from '../../../util/Transform';
+import { undoable, undoBatch } from '../../../util/UndoManager';
+import { EditableView } from '../../EditableView';
+import { Colors } from '../../global/globalEnums';
+import { OpenWhere } from '../../nodes/DocumentView';
+import { FieldView, FieldViewProps } from '../../nodes/FieldView';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { KeyValueBox } from '../../nodes/KeyValueBox';
+import { DefaultStyleProvider } from '../../StyleProvider';
+import { ColumnType, FInfotoColType } from './CollectionSchemaView';
+import './CollectionSchemaView.scss';
+
+export interface SchemaTableCellProps {
+ Document: Doc;
+ col: number;
+ deselectCell: () => void;
+ selectCell: (doc: Doc, col: number) => void;
+ selectedCell: () => [Doc, number] | undefined;
+ fieldKey: string;
+ maxWidth?: () => number;
+ columnWidth: () => number;
+ rowHeight: () => number;
+ padding?: number; // default is 5 -- see scss
+ isRowActive: () => boolean | undefined;
+ getFinfo: (fieldKey: string) => FInfo | undefined;
+ setColumnValues: (field: string, value: string) => boolean;
+ oneLine?: boolean; // whether all input should fit on one line vs allowing textare multiline inputs
+ allowCRs?: boolean; // allow carriage returns in text input (othewrise CR ends the edit)
+ finishEdit?: () => void; // notify container that edit is over (eg. to hide view in DashFieldView)
+}
+
+@observer
+export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
+ static addFieldDoc = (doc: Doc, where: OpenWhere) => {
+ DocFocusOrOpen(doc);
+ return true;
+ };
+ public static renderProps(props: SchemaTableCellProps) {
+ const { Document, fieldKey, getFinfo, columnWidth, isRowActive } = props;
+ let protoCount = 0;
+ let doc: Doc | undefined = Document;
+ while (doc) {
+ if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) {
+ break;
+ }
+ protoCount++;
+ doc = doc.proto;
+ }
+ const parenCount = Math.max(0, protoCount - 1);
+ const color = protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue';
+ const textDecoration = color !== 'black' && parenCount ? 'underline' : '';
+ const fieldProps: FieldViewProps = {
+ docFilters: returnEmptyFilter,
+ docRangeFilters: returnEmptyFilter,
+ searchFilterDocs: returnEmptyDoclist,
+ styleProvider: DefaultStyleProvider,
+ docViewPath: returnEmptyDoclist,
+ rootSelected: returnFalse,
+ isSelected: returnFalse,
+ setHeight: returnFalse,
+ select: emptyFunction,
+ dropAction: 'embed',
+ bringToFront: emptyFunction,
+ renderDepth: 1,
+ isContentActive: returnFalse,
+ whenChildContentsActiveChanged: emptyFunction,
+ ScreenToLocalTransform: Transform.Identity,
+ focus: emptyFunction,
+ addDocTab: SchemaTableCell.addFieldDoc,
+ pinToPres: returnZero,
+ Document,
+ fieldKey: fieldKey,
+ PanelWidth: columnWidth,
+ PanelHeight: props.rowHeight,
+ };
+ const readOnly = getFinfo(fieldKey)?.readOnly ?? false;
+ const cursor = !readOnly ? 'text' : 'default';
+ const pointerEvents: 'all' | 'none' = !readOnly && isRowActive() ? 'all' : 'none';
+ return { color, textDecoration, fieldProps, cursor, pointerEvents };
+ }
+
+ @computed get selected() {
+ const selected: [Doc, number] | undefined = this.props.selectedCell();
+ return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+ }
+
+ @computed get defaultCellContent() {
+ const { color, textDecoration, fieldProps } = SchemaTableCell.renderProps(this.props);
+
+ return (
+ <div
+ className="schemacell-edit-wrapper"
+ style={{
+ color,
+ textDecoration,
+ width: '100%',
+ }}>
+ <EditableView
+ oneLine={this.props.oneLine}
+ allowCRs={this.props.allowCRs}
+ contents={<FieldView {...fieldProps} />}
+ editing={this.selected ? undefined : false}
+ GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+ SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => {
+ if (shiftDown && enterKey) {
+ this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
+ }
+ const ret = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
+ this.props.finishEdit?.();
+ return ret;
+ }, 'edit schema cell')}
+ />
+ </div>
+ );
+ }
+
+ get getCellType() {
+ const cellValue = this.props.Document[this.props.fieldKey];
+ if (cellValue instanceof ImageField) return ColumnType.Image;
+ if (cellValue instanceof DateField) return ColumnType.Date;
+ if (cellValue instanceof RichTextField) return ColumnType.RTF;
+ if (typeof cellValue === 'number') return ColumnType.Any;
+ if (typeof cellValue === 'string') return ColumnType.Any;
+ if (typeof cellValue === 'boolean') return ColumnType.Boolean;
+
+ const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType;
+ if (columnTypeStr && columnTypeStr in FInfotoColType) {
+ return FInfotoColType[columnTypeStr];
+ }
+
+ return ColumnType.Any;
+ }
+
+ get content() {
+ const cellType: ColumnType = this.getCellType;
+ // prettier-ignore
+ switch (cellType) {
+ case ColumnType.Image: return <SchemaImageCell {...this.props} />;
+ case ColumnType.Boolean: return <SchemaBoolCell {...this.props} />;
+ case ColumnType.RTF: return <SchemaRTFCell {...this.props} />;
+ case ColumnType.Date: // return <SchemaDateCell {...this.props} />;
+ default: return this.defaultCellContent;
+ }
+ }
+
+ render() {
+ return (
+ <div
+ className="schema-table-cell"
+ onPointerDown={action(e => !this.selected && this.props.selectCell(this.props.Document, this.props.col))}
+ style={{ padding: this.props.padding, maxWidth: this.props.maxWidth?.(), width: this.props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
+ {this.content}
+ </div>
+ );
+ }
+}
+
+// mj: most of this is adapted from old schema code so I'm not sure what it does tbh
+@observer
+export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
+ @observable _previewRef: HTMLImageElement | undefined;
+
+ 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, '_s' + ext);
+ }
+
+ get url() {
+ const field = Cast(this.props.Document[this.props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+ const alts = DocListCast(this.props.Document[this.props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
+ const 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')];
+ return url[0];
+ }
+
+ @action
+ showHoverPreview = (e: React.PointerEvent) => {
+ this._previewRef = document.createElement('img');
+ document.body.appendChild(this._previewRef);
+ const ext = extname(this.url);
+ this._previewRef.src = this.url.replace('_s' + ext, '_m' + ext);
+ this._previewRef.style.position = 'absolute';
+ this._previewRef.style.left = e.clientX + 10 + 'px';
+ this._previewRef.style.top = e.clientY + 10 + 'px';
+ this._previewRef.style.zIndex = '1000';
+ };
+
+ @action
+ moveHoverPreview = (e: React.PointerEvent) => {
+ if (!this._previewRef) return;
+ this._previewRef.style.left = e.clientX + 10 + 'px';
+ this._previewRef.style.top = e.clientY + 10 + 'px';
+ };
+
+ @action
+ removeHoverPreview = (e: React.PointerEvent) => {
+ if (!this._previewRef) return;
+ document.body.removeChild(this._previewRef);
+ };
+
+ render() {
+ const aspect = Doc.NativeAspect(this.props.Document); // aspect ratio
+ // let width = Math.max(75, this.props.columnWidth); // get a with that is no smaller than 75px
+ // const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px
+ const height = this.props.rowHeight() ? this.props.rowHeight() - (this.props.padding || 6) * 2 : undefined;
+ const width = height ? height * aspect : undefined; // increase the width of the image if necessary to maintain proportionality
+
+ return <img src={this.url} width={width} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
+ }
+}
+
+@observer
+export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
+ @observable _pickingDate: boolean = false;
+
+ @computed get date(): DateField {
+ // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
+ return DateCast(this.props.Document[this.props.fieldKey]);
+ }
+
+ @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.props.Document[this.props.fieldKey] = new DateField(date as Date);
+ //}
+ };
+
+ render() {
+ return <DatePicker dateFormat={'Pp'} selected={this.date.date} onChange={(date: any) => this.handleChange(date)} />;
+ }
+}
+@observer
+export class SchemaRTFCell extends React.Component<SchemaTableCellProps> {
+ @computed get selected() {
+ const selected: [Doc, number] | undefined = this.props.selectedCell();
+ return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+ }
+ selectedFunc = () => this.selected;
+ render() {
+ const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
+ fieldProps.isContentActive = this.selectedFunc;
+ return (
+ <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}>
+ {this.selected ? <FormattedTextBox allowScroll={true} {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+ </div>
+ );
+ }
+}
+@observer
+export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
+ @computed get selected() {
+ const selected: [Doc, number] | undefined = this.props.selectedCell();
+ return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+ }
+ render() {
+ const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
+ return (
+ <div className="schemaBoolCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}>
+ <input
+ style={{ marginRight: 4 }}
+ type="checkbox"
+ checked={BoolCast(this.props.Document[this.props.fieldKey])}
+ onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => {
+ if ((value?.nativeEvent as any).shiftKey) {
+ this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+ }
+ KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+ })}
+ />
+ <EditableView
+ contents={<FieldView {...fieldProps} />}
+ editing={this.selected ? undefined : false}
+ GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+ SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => {
+ if (shiftDown && enterKey) {
+ this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
+ }
+ const set = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
+ this.props.finishEdit?.();
+ return set;
+ })}
+ />
+ </div>
+ );
+ }
+}