aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authorHannah <hannah_chow@brown.edu>2020-02-11 06:34:19 -0500
committerHannah <hannah_chow@brown.edu>2020-02-11 06:34:19 -0500
commitb60154479add51f826afaeff074cf43a6b416831 (patch)
tree723f7f1356b1c3dcf5c409347b125d5a577b538f /src/client/views/collections
parent74ca8ac01e32faa8adab54ddfd806fe46cda03ec (diff)
parentdc6453e27375a1b3d614a74b7fd1d83695f130d7 (diff)
merge
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionCarouselView.scss40
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx82
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx94
-rw-r--r--src/client/views/collections/CollectionLinearView.scss75
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx134
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx4
-rw-r--r--src/client/views/collections/CollectionPivotView.scss88
-rw-r--r--src/client/views/collections/CollectionPivotView.tsx148
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx18
-rw-r--r--src/client/views/collections/CollectionSchemaHeaders.tsx3
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx23
-rw-r--r--src/client/views/collections/CollectionStackingView.scss2
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx90
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx68
-rw-r--r--src/client/views/collections/CollectionStaffView.tsx4
-rw-r--r--src/client/views/collections/CollectionSubView.tsx108
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx177
-rw-r--r--src/client/views/collections/CollectionView.tsx110
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss40
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx128
-rw-r--r--src/client/views/collections/ParentDocumentSelector.scss2
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx33
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx34
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx446
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx11
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx113
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss33
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx258
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx116
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx56
31 files changed, 2017 insertions, 533 deletions
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
new file mode 100644
index 000000000..4815f1a59
--- /dev/null
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -0,0 +1,40 @@
+
+.collectionCarouselView-outer {
+ background: gray;
+ .collectionCarouselView-caption {
+ margin-left: 10%;
+ margin-right: 10%;
+ height: 50;
+ display: inline-block;
+ width: 80%;
+ }
+ .collectionCarouselView-image {
+ height: calc(100% - 50px);
+ display: inline-block;
+ width: 100%;
+ }
+}
+.carouselView-back {
+ position: absolute;
+ display: flex;
+ left: 0;
+ top: 50%;
+ width: 30;
+ height: 30;
+ background: lightgray;
+ align-items: center;
+ border-radius: 5px;
+ justify-content: center;
+}
+.carouselView-fwd {
+ position: absolute;
+ display: flex;
+ right: 0;
+ top: 50%;
+ width: 30;
+ height: 30;
+ background: lightgray;
+ align-items: center;
+ border-radius: 5px;
+ justify-content: center;
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
new file mode 100644
index 000000000..0933d5924
--- /dev/null
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -0,0 +1,82 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { observable, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { makeInterface } from '../../../new_fields/Schema';
+import { NumCast, StrCast } from '../../../new_fields/Types';
+import { DragManager } from '../../util/DragManager';
+import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
+import "./CollectionCarouselView.scss";
+import { CollectionSubView } from './CollectionSubView';
+import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons';
+import { Doc } from '../../../new_fields/Doc';
+import { FormattedTextBox } from '../nodes/FormattedTextBox';
+
+
+
+
+type CarouselDocument = makeInterface<[typeof documentSchema,]>;
+const CarouselDocument = makeInterface(documentSchema);
+
+@observer
+export class CollectionCarouselView extends CollectionSubView(CarouselDocument) {
+ @observable public addMenuToggle = React.createRef<HTMLInputElement>();
+ private _dropDisposer?: DragManager.DragDropDisposer;
+
+ componentWillUnmount() {
+ this._dropDisposer && this._dropDisposer();
+ }
+
+ componentDidMount() {
+ }
+ protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ this._dropDisposer && this._dropDisposer();
+ if (ele) {
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+ }
+ }
+
+ advance = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) + 1) % this.childLayoutPairs.length;
+ }
+ goback = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
+ }
+
+ panelHeight = () => this.props.PanelHeight() - 50;
+ @computed get content() {
+ const index = NumCast(this.layoutDoc._itemIndex);
+ return !(this.childLayoutPairs?.[index]?.layout instanceof Doc) ? (null) :
+ <div>
+ <div className="collectionCarouselView-image">
+ <ContentFittingDocumentView {...this.props}
+ Document={this.childLayoutPairs[index].layout}
+ DataDocument={this.childLayoutPairs[index].data}
+ PanelHeight={this.panelHeight}
+ getTransform={this.props.ScreenToLocalTransform} />
+ </div>
+ <div className="collectionCarouselView-caption" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}>
+ <FormattedTextBox key={index} {...this.props} Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox>
+ </div>
+ </div>
+ }
+ @computed get buttons() {
+ return <>
+ <div key="back" className="carouselView-back" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={this.goback}>
+ <FontAwesomeIcon icon={faCaretLeft} size={"2x"} />
+ </div>
+ <div key="fwd" className="carouselView-fwd" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={this.advance}>
+ <FontAwesomeIcon icon={faCaretRight} size={"2x"} />
+ </div>
+ </>;
+ }
+ render() {
+ return <div className="collectionCarouselView-outer">
+ {this.content}
+ {this.buttons}
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 151b84c50..82cb3bc88 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -34,6 +34,7 @@ import { DocumentType } from '../../documents/DocumentTypes';
import { ComputedField } from '../../../new_fields/ScriptField';
import { InteractionUtils } from '../../util/InteractionUtils';
import { TraceMobx } from '../../../new_fields/util';
+import { Scripting } from '../../util/Scripting';
library.add(faFile);
const _global = (window /* browser */ || global /* node */) as any;
@@ -131,7 +132,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@undoBatch
@action
- public static CloseRightSplit(document: Doc): boolean {
+ public static CloseRightSplit(document: Opt<Doc>): boolean {
if (!CollectionDockingView.Instance) return false;
const instance = CollectionDockingView.Instance;
let retVal = false;
@@ -139,14 +140,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId) &&
- Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) {
+ ((!document && DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document.isDisplayPanel) ||
+ (document && Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)))) {
child.contentItems[0].remove();
instance.layoutChanged(document);
return true;
} else {
Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId) &&
- Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
+ ((!document && DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document.isDisplayPanel) ||
+ (document && Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)))) {
child.contentItems[j].remove();
child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
return true;
@@ -171,13 +174,52 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (removed) CollectionDockingView.Instance._removedDocs.push(removed);
this.stateChanged();
}
+ @undoBatch
+ @action
+ public static ReplaceRightSplit(document: Doc, dataDoc: Doc | undefined, libraryPath?: Doc[]): boolean {
+ if (!CollectionDockingView.Instance) return false; const instance = CollectionDockingView.Instance;
+ const newItemStackConfig = {
+ type: 'stack',
+ content: [CollectionDockingView.makeDocumentConfig(document, dataDoc, undefined, libraryPath)]
+ };
+
+ const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+
+ let retVal = false;
+ if (instance._goldenLayout.root.contentItems[0].isRow) {
+ retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
+ if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
+ DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)?.Document.isDisplayPanle) {
+ child.contentItems[0].remove();
+ child.addChild(newContentItem, undefined, true);
+ instance.layoutChanged(document);
+ return true;
+ } else {
+ Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
+ if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)?.Document.isDisplayPanel) {
+ child.contentItems[j].remove();
+ child.addChild(newContentItem, undefined, true);
+ return true;
+ }
+ return false;
+ });
+ }
+ return false;
+ });
+ }
+ if (retVal) {
+ instance.stateChanged();
+ }
+ return retVal;
+ }
+
//
- // Creates a vertical split on the right side of the docking view, and then adds the Document to that split
+ // Creates a vertical split on the right side of the docking view, and then adds the Document to the right of that split
//
@undoBatch
@action
- public static AddRightSplit(document: Doc, dataDoc: Doc | undefined, minimize: boolean = false, libraryPath?: Doc[]) {
+ public static AddRightSplit(document: Doc, dataDoc: Doc | undefined, libraryPath?: Doc[]) {
if (!CollectionDockingView.Instance) return false;
const instance = CollectionDockingView.Instance;
const newItemStackConfig = {
@@ -202,16 +244,23 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
collayout.config.width = 50;
newContentItem.config.width = 50;
}
- if (minimize) {
- // bcz: this makes the drag image show up better, but it also messes with fixed layout sizes
- // newContentItem.config.width = 10;
- // newContentItem.config.height = 10;
- }
newContentItem.callDownwards('_$init');
instance.layoutChanged();
return true;
}
+ //
+ // Creates a vertical split on the right side of the docking view, and then adds the Document to that split
+ //
+ @undoBatch
+ @action
+ public static UseRightSplit(document: Doc, dataDoc: Doc | undefined, libraryPath?: Doc[]) {
+ document.isDisplayPanel = true;
+ if (!CollectionDockingView.ReplaceRightSplit(document, dataDoc, libraryPath)) {
+ CollectionDockingView.AddRightSplit(document, dataDoc, libraryPath);
+ }
+ }
+
@undoBatch
@action
public AddTab = (stack: any, document: Doc, dataDocument: Doc | undefined, libraryPath?: Doc[]) => {
@@ -476,7 +525,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
stack.header.element[0].style.backgroundColor = DocServer.Control.isReadOnly() ? "#228540" : undefined;
stack.header.element.on('mousedown', (e: any) => {
if (e.target === stack.header.element[0] && e.button === 1) {
- this.AddTab(stack, Docs.Create.FreeformDocument([], { width: this.props.PanelWidth(), height: this.props.PanelHeight(), title: "Untitled Collection" }), undefined);
+ this.AddTab(stack, Docs.Create.FreeformDocument([], { _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: "Untitled Collection" }), undefined);
}
});
@@ -596,7 +645,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
if (curPres) {
const pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" });
Doc.GetProto(pinDoc).presentationTargetDoc = doc;
- Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()');
+ Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title?.toString()');
const data = Cast(curPres.data, listSpec(Doc));
if (data) {
data.push(pinDoc);
@@ -636,19 +685,19 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
get layoutDoc() { return this._document && Doc.Layout(this._document); }
- panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc.width), NumCast(this.layoutDoc.nativeWidth)), this._panelWidth) : this._panelWidth;
+ panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) : this._panelWidth;
panelHeight = () => this._panelHeight;
- nativeWidth = () => !this.layoutDoc!.ignoreAspect && !this.layoutDoc!.fitWidth ? NumCast(this.layoutDoc!.nativeWidth) || this._panelWidth : 0;
- nativeHeight = () => !this.layoutDoc!.ignoreAspect && !this.layoutDoc!.fitWidth ? NumCast(this.layoutDoc!.nativeHeight) || this._panelHeight : 0;
+ nativeWidth = () => !this.layoutDoc!.ignoreAspect && !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeWidth) || this._panelWidth : 0;
+ nativeHeight = () => !this.layoutDoc!.ignoreAspect && !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeHeight) || this._panelHeight : 0;
contentScaling = () => {
if (this.layoutDoc!.type === DocumentType.PDF) {
- if ((this.layoutDoc && this.layoutDoc.fitWidth) ||
- this._panelHeight / NumCast(this.layoutDoc!.nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!.nativeWidth)) {
- return this._panelWidth / NumCast(this.layoutDoc!.nativeWidth);
+ if ((this.layoutDoc && this.layoutDoc._fitWidth) ||
+ this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth)) {
+ return this._panelWidth / NumCast(this.layoutDoc!._nativeWidth);
} else {
- return this._panelHeight / NumCast(this.layoutDoc!.nativeHeight);
+ return this._panelHeight / NumCast(this.layoutDoc!._nativeHeight);
}
}
const nativeH = this.nativeHeight();
@@ -674,7 +723,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
if (doc.dockingConfig) {
return MainView.Instance.openWorkspace(doc);
} else if (location === "onRight") {
- return CollectionDockingView.AddRightSplit(doc, dataDoc, undefined, libraryPath);
+ return CollectionDockingView.AddRightSplit(doc, dataDoc, libraryPath);
} else if (location === "close") {
return CollectionDockingView.CloseRightSplit(doc);
} else {
@@ -694,7 +743,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
bringToFront={emptyFunction}
addDocument={undefined}
removeDocument={undefined}
- ruleProvider={undefined}
ContentScaling={this.contentScaling}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
@@ -717,10 +765,12 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
(<div className="collectionDockingView-content" ref={ref => this._mainCont = ref}
style={{
transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`,
- height: this.layoutDoc && this.layoutDoc.fitWidth ? undefined : "100%",
+ height: this.layoutDoc && this.layoutDoc._fitWidth ? undefined : "100%",
width: this.widthpercent
}}>
{this.docView}
</div >);
}
}
+Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc, undefined); });
+Scripting.addGlobal(function useRightSplit(doc: any) { CollectionDockingView.UseRightSplit(doc, undefined); });
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
new file mode 100644
index 000000000..eae9e0220
--- /dev/null
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -0,0 +1,75 @@
+@import "../globalCssVariables";
+@import "../_nodeModuleOverrides";
+
+.collectionLinearView-outer{
+ overflow: hidden;
+ height:100%;
+ .collectionLinearView {
+ display:flex;
+ height: 100%;
+ >label {
+ background: $dark-color;
+ color: $light-color;
+ display: inline-block;
+ border-radius: 18px;
+ font-size: 12.5px;
+ width: 18px;
+ height: 18px;
+ margin-top:auto;
+ margin-bottom:auto;
+ margin-right: 3px;
+ cursor: pointer;
+ transition: transform 0.2s;
+ }
+
+ label p {
+ padding-left:5px;
+ }
+
+ label:hover {
+ background: $main-accent;
+ transform: scale(1.15);
+ }
+
+ >input {
+ display: none;
+ }
+ >input:not(:checked)~.collectionLinearView-content {
+ display: none;
+ }
+
+ >input:checked~label {
+ transform: rotate(45deg);
+ transition: transform 0.5s;
+ cursor: pointer;
+ }
+
+ .collectionLinearView-content {
+ display: flex;
+ opacity: 1;
+ position: relative;
+ margin-top: auto;
+
+ .collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable {
+ position:relative;
+ margin:auto;
+ margin-left: 3px;
+ transform-origin: center 80%;
+ }
+ .collectionLinearView-docBtn-scalable:hover {
+ transform: scale(1.15);
+ }
+
+ .collectionLinearView-round-button {
+ width: 18px;
+ height: 18px;
+ border-radius: 18px;
+ font-size: 15px;
+ }
+
+ .collectionLinearView-round-button:hover {
+ transform: scale(1.15);
+ }
+ }
+ }
+}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
new file mode 100644
index 000000000..e613bf411
--- /dev/null
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -0,0 +1,134 @@
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc';
+import { makeInterface } from '../../../new_fields/Schema';
+import { BoolCast, NumCast, StrCast, Cast } from '../../../new_fields/Types';
+import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../../Utils';
+import { DragManager } from '../../util/DragManager';
+import { Transform } from '../../util/Transform';
+import "./CollectionLinearView.scss";
+import { CollectionViewType } from './CollectionView';
+import { CollectionSubView } from './CollectionSubView';
+import { DocumentView } from '../nodes/DocumentView';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { Id } from '../../../new_fields/FieldSymbols';
+import { ScriptField } from '../../../new_fields/ScriptField';
+
+
+type LinearDocument = makeInterface<[typeof documentSchema,]>;
+const LinearDocument = makeInterface(documentSchema);
+
+@observer
+export class CollectionLinearView extends CollectionSubView(LinearDocument) {
+ @observable public addMenuToggle = React.createRef<HTMLInputElement>();
+ @observable private _selectedIndex = -1;
+ private _dropDisposer?: DragManager.DragDropDisposer;
+ private _widthDisposer?: IReactionDisposer;
+ private _selectedDisposer?: IReactionDisposer;
+
+ componentWillUnmount() {
+ this._dropDisposer && this._dropDisposer();
+ this._widthDisposer && this._widthDisposer();
+ this._selectedDisposer && this._selectedDisposer();
+ this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => {
+ Cast(pair.layout.proto?.onPointerUp, ScriptField)?.script.run({ this: pair.layout.proto }, console.log);
+ });
+ }
+
+ componentDidMount() {
+ // is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported).
+ this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
+ () => this.props.Document._width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+ { fireImmediately: true }
+ );
+
+ this._selectedDisposer = reaction(
+ () => NumCast(this.props.Document.selectedIndex),
+ (i) => runInAction(() => {
+ this._selectedIndex = i;
+ let selected: any = undefined;
+ this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map(async (pair, ind) => {
+ const isSelected = this._selectedIndex === ind;
+ if (isSelected) {
+ selected = pair;
+ }
+ else {
+ Cast(pair.layout.proto?.onPointerUp, ScriptField)?.script.run({ this: pair.layout.proto }, console.log);
+ }
+ });
+ if (selected && selected.layout) {
+ Cast(selected.layout.proto?.onPointerDown, ScriptField)?.script.run({ this: selected.layout.proto }, console.log);
+ }
+ }),
+ { fireImmediately: true }
+ );
+ }
+ protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ this._dropDisposer && this._dropDisposer();
+ if (ele) {
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+ }
+ }
+
+ public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
+
+ dimension = () => NumCast(this.props.Document._height); // 2 * the padding
+ getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
+ if (!ele.current) return Transform.Identity();
+ const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
+ return new Transform(-translateX, -translateY, 1 / scale);
+ }
+
+ render() {
+ const guid = Utils.GenerateGuid();
+ const flexDir: any = StrCast(this.Document.flexDirection);
+ return <div className="collectionLinearView-outer">
+ <div className="collectionLinearView" ref={this.createDashEventsTarget} >
+ <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle}
+ onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
+ <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
+
+ <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document._width, 25), flexDirection: flexDir }}>
+ {this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => {
+ const nested = pair.layout._viewType === CollectionViewType.Linear;
+ const dref = React.createRef<HTMLDivElement>();
+ const nativeWidth = NumCast(pair.layout._nativeWidth, this.dimension());
+ const deltaSize = nativeWidth * .15 / 2;
+ return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
+ style={{
+ width: nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize,
+ height: nested && pair.layout.isExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
+ }} >
+ <DocumentView
+ Document={pair.layout}
+ DataDoc={pair.data}
+ LibraryPath={this.props.LibraryPath}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={emptyFunction}
+ removeDocument={this.props.removeDocument}
+ onClick={undefined}
+ ScreenToLocalTransform={this.getTransform(dref)}
+ ContentScaling={returnOne}
+ PanelWidth={nested ? pair.layout[WidthSym] : () => this.dimension()}// ugh - need to get rid of this inline function to avoid recomputing
+ PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()}
+ renderDepth={this.props.renderDepth + 1}
+ focus={emptyFunction}
+ backgroundColor={returnEmptyString}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ zoomToScale={emptyFunction}
+ getScale={returnOne}>
+ </DocumentView>
+ </div>;
+ })}
+ </div>
+ </div>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 26e0cc35a..e84b3b0dd 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -137,7 +137,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
addDocument = (value: string, shiftDown?: boolean) => {
this._createAliasSelected = false;
const key = StrCast(this.props.parent.props.Document.sectionFilter);
- const newDoc = Docs.Create.TextDocument({ height: 18, width: 200, title: value });
+ const newDoc = Docs.Create.TextDocument("", { _height: 18, _width: 200, title: value });
newDoc[key] = this.getValue(this.props.heading);
return this.props.parent.props.addDocument(newDoc);
}
@@ -259,7 +259,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@computed get contentLayout() {
const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap))));
const style = this.props.parent; const collapsed = this._collapsed;
- const chromeStatus = this.props.parent.props.Document.chromeStatus;
+ const chromeStatus = this.props.parent.props.Document._chromeStatus;
const newEditableViewProps = {
GetValue: () => "",
SetValue: this.addDocument,
diff --git a/src/client/views/collections/CollectionPivotView.scss b/src/client/views/collections/CollectionPivotView.scss
new file mode 100644
index 000000000..505091e98
--- /dev/null
+++ b/src/client/views/collections/CollectionPivotView.scss
@@ -0,0 +1,88 @@
+.collectionPivotView {
+ display: flex;
+ flex-direction: row;
+ position: absolute;
+ height: 100%;
+ width: 100%;
+
+ .collectionPivotView-flyout {
+ width: 400px;
+ height: 300px;
+ display: inline-block;
+
+ .collectionPivotView-flyout-item {
+ background-color: lightgray;
+ text-align: left;
+ display: inline-block;
+ position: relative;
+ width: 100%;
+ }
+ }
+
+ .pivotKeyEntry {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ z-index: 10;
+ pointer-events: all;
+ padding: 5px;
+ border: 1px solid black;
+ }
+
+ .collectionPivotView-treeView {
+ display: flex;
+ flex-direction: column;
+ width: 200px;
+ height: 100%;
+
+ .collectionPivotView-addfacet {
+ display: inline-block;
+ width: 200px;
+ height: 30px;
+ background: darkGray;
+ text-align: center;
+
+ .collectionPivotView-button {
+ align-items: center;
+ display: flex;
+ width: 100%;
+ height: 100%;
+
+ .collectionPivotView-span {
+ margin: auto;
+ }
+ }
+
+ >div,
+ >div>div {
+ width: 100%;
+ height: 100%;
+ text-align: center;
+ }
+ }
+
+ .collectionPivotView-tree {
+ display: inline-block;
+ width: 100%;
+ height: calc(100% - 30px);
+ }
+ }
+
+ .collectionPivotView-pivot {
+ display: inline-block;
+ width: calc(100% - 200px);
+ height: 100%;
+ }
+
+ .collectionPivotView-dragger {
+ background-color: lightgray;
+ height: 40px;
+ width: 20px;
+ position: absolute;
+ border-radius: 10px;
+ top: 55%;
+ border: 1px black solid;
+ z-index: 2;
+ left: -10px;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
new file mode 100644
index 000000000..440b6856b
--- /dev/null
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -0,0 +1,148 @@
+import { faEdit } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, IReactionDisposer, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Set } from "typescript-collections";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
+import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
+import { Cast, StrCast } from "../../../new_fields/Types";
+import { Docs } from "../../documents/Documents";
+import { EditableView } from "../EditableView";
+import { anchorPoints, Flyout } from "../TemplateMenu";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import "./CollectionPivotView.scss";
+import { CollectionSubView } from "./CollectionSubView";
+import { CollectionTreeView } from "./CollectionTreeView";
+import React = require("react");
+
+@observer
+export class CollectionPivotView extends CollectionSubView(doc => doc) {
+ componentDidMount() {
+ this.props.Document._freeformLayoutEngine = "pivot";
+ const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
+ if (!this.props.Document._facetCollection) {
+ const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true });
+ facetCollection.target = this.props.Document;
+ this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilter"]);
+
+ const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
+ const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailed'); useRightSplit(alias); ";
+ facetCollection.onCheckedClick = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "boolean", checked: "boolean", containingTreeView: Doc.name });
+ this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "boolean", containingCollection: Doc.name });
+ this.props.Document._facetCollection = facetCollection;
+ this.props.Document._fitToBox = true;
+ }
+ }
+ bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth;
+ getTransform = () => this.props.ScreenToLocalTransform().translate(-200, 0);
+
+ @computed get _allFacets() {
+ const facets = new Set<string>();
+ this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
+ return facets.toArray();
+ }
+
+ /**
+ * Responds to clicking the check box in the flyout menu
+ */
+ facetClick = (facetHeader: string) => {
+ const facetCollection = this.props.Document._facetCollection;
+ if (facetCollection instanceof Doc) {
+ const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
+ if (found !== -1) {
+ (facetCollection.data as List<Doc>).splice(found, 1);
+ const docFilter = Cast(this.props.Document._docFilter, listSpec("string"));
+ if (docFilter) {
+ let index: number;
+ while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
+ docFilter.splice(index, 3);
+ }
+ }
+ } else {
+ const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
+ const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
+ const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
+ newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
+ Doc.AddDocToList(facetCollection, "data", newFacet);
+ }
+ }
+ }
+ _canClick = false;
+ _facetWidthOnDown = 0;
+ @observable _facetWidth = 200;
+ onPointerDown = (e: React.PointerEvent) => {
+ this._canClick = true;
+ this._facetWidthOnDown = e.screenX;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+
+ @action
+ onPointerMove = (e: PointerEvent) => {
+ this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
+ Math.abs(e.movementX) > 6 && (this._canClick = false);
+ }
+ @action
+ onPointerUp = (e: PointerEvent) => {
+ if (Math.abs(e.screenX - this._facetWidthOnDown) < 6 && this._canClick) {
+ this._facetWidth = this._facetWidth < 15 ? 200 : 0;
+ }
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ render() {
+ const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
+ const flyout = (
+ <div className="collectionPivotView-flyout" style={{ width: `${this._facetWidth}` }}>
+ {this._allFacets.map(facet => <label className="collectionPivotView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
+ <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} />
+ <span className="checkmark" />
+ {facet}
+ </label>)}
+ </div>
+ );
+ return !facetCollection ? (null) :
+ <div className="collectionPivotView" style={{ height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+ <div className={"pivotKeyEntry"}>
+ <EditableView
+ contents={this.props.Document.pivotField}
+ GetValue={() => StrCast(this.props.Document.pivotField)}
+ SetValue={value => {
+ if (value && value.length) {
+ this.props.Document.pivotField = value;
+ return true;
+ }
+ return false;
+ }}
+ />
+ </div>
+ <div className="collectionPivotView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
+ <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
+ </div>
+ <div className="collectionPivotView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
+ <div className="collectionPivotView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}>
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
+ <div className="collectionPivotView-button">
+ <span className="collectionPivotView-span">Facet Filters</span>
+ <FontAwesomeIcon icon={faEdit} size={"lg"} />
+ </div>
+ </Flyout>
+ </div>
+ <div className="collectionPivotView-tree" key="tree">
+ <CollectionTreeView {...this.props} Document={facetCollection} />
+ </div>
+ </div>
+ <div className="collectionPivotView-pivot" key="pivot" style={{ width: this.bodyPanelWidth() }}>
+ <CollectionFreeFormView {...this.props} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
+ </div>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 79a34bc00..4eba5dc26 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -23,6 +23,7 @@ import { faExpand } from '@fortawesome/free-solid-svg-icons';
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import { KeyCodes } from "../../northstar/utils/KeyCodes";
import { undoBatch } from "../../util/UndoManager";
+import { List } from "lodash";
library.add(faExpand);
@@ -82,10 +83,20 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
}
@action
- onPointerDown = (e: React.PointerEvent): void => {
+ onPointerDown = async (e: React.PointerEvent): Promise<void> => {
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)) {
+ try {
+ new URL(url);
+ const temp = window.open(url)!;
+ temp.blur();
+ window.focus();
+ } catch { }
+ }
+
// this._isEditing = true;
// this.props.setIsEditing(true);
@@ -145,7 +156,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
DataDoc: this.props.rowProps.original,
LibraryPath: [],
fieldKey: this.props.rowProps.column.id as string,
- ruleProvider: undefined,
ContainingCollectionView: this.props.CollectionView,
ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
isSelected: returnFalse,
@@ -226,14 +236,14 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
if (value.startsWith(":=")) {
return this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col);
}
- const script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+ const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (!script.compiled) {
return false;
}
return this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
}}
OnFillDown={async (value: string) => {
- const script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+ const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (script.compiled) {
DocListCast(this.props.Document[this.props.fieldKey]).
forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, script.run));
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index 0114342b9..92dc8780e 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -286,7 +286,6 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
}
@undoBatch
- @action
onKeyDown = (e: React.KeyboardEvent): void => {
if (e.key === "Enter") {
const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
@@ -296,7 +295,7 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) {
this.onSelect(this._searchTerm);
} else {
- this._searchTerm = this._key;
+ this.setSearchTerm(this._key);
}
}
}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index bb706e528..fa8be5177 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -144,7 +144,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
LibraryPath={this.props.LibraryPath}
childDocs={this.childDocs}
renderDepth={this.props.renderDepth}
- ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
PanelWidth={this.previewWidth}
PanelHeight={this.previewHeight}
getTransform={this.getPreviewTransform}
@@ -157,8 +156,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
pinToPres={this.props.pinToPres}
- setPreviewScript={this.setPreviewScript}
- previewScript={this.previewScript}
/>
</div>;
}
@@ -480,7 +477,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@undoBatch
createRow = () => {
- const newDoc = Docs.Create.TextDocument({ title: "", width: 100, height: 30 });
+ const newDoc = Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 });
this.props.addDocument(newDoc);
}
@@ -627,6 +624,19 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return Array.from(Object.keys(keys));
}
+ @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;
@@ -645,7 +655,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const expanded = {};
//@ts-ignore
expandedRowsList.forEach(row => expanded[row] = true);
- console.log("text wrapped rows", ...[...this.textWrappedRows]); // TODO: get component to rerender on text wrap change without needign to console.log :((((
+ const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :((((
return <ReactTable
style={{ position: "relative" }}
@@ -681,7 +691,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
+ // ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
+ ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" })
}
}
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index e1577cfee..843c743db 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -385,4 +385,4 @@
.rc-switch-checked .rc-switch-inner {
left: 8px;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 83c90810e..7592712e4 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,7 +4,7 @@ import { CursorProperty } from "csstype";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import Switch from 'rc-switch';
-import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
+import { Doc, HeightSym, WidthSym, DataSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
@@ -22,7 +22,6 @@ import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewField
import { CollectionSubView } from "./CollectionSubView";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
-import { ScriptBox } from "../ScriptBox";
import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
import { TraceMobx } from "../../../new_fields/util";
import { CollectionViewType } from "./CollectionView";
@@ -40,35 +39,31 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@observable _scroll = 0; // used to force the document decoration to update when scrolling
@computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
@computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
- @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
- @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
- @computed get yMargin() { return Math.max(this.props.Document.showTitle ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); }
- @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
+ @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout); }
+ @computed get xMargin() { return NumCast(this.props.Document._xMargin, 2 * this.gridGap); }
+ @computed get yMargin() { return Math.max(this.props.Document.showTitle && !this.props.Document.showTitleHover ? 30 : 0, NumCast(this.props.Document._yMargin, 2 * this.gridGap)); }
+ @computed get gridGap() { return NumCast(this.props.Document._gridGap, 10); }
@computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); }
@computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
- @computed get showAddAGroup() { return (this.sectionFilter && (this.props.Document.chromeStatus !== 'view-mode' && this.props.Document.chromeStatus !== 'disabled')); }
+ @computed get showAddAGroup() { return (this.sectionFilter && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); }
@computed get columnWidth() {
return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin,
this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250));
}
@computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
- childDocHeight(child: Doc) { return this.getDocHeight(Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, child).layout); }
-
children(docs: Doc[]) {
this._docXfs.length = 0;
return docs.map((d, i) => {
- const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d);
- const layoutDoc = pair.layout ? Doc.Layout(pair.layout) : d;
- const width = () => Math.min(layoutDoc.nativeWidth && !layoutDoc.ignoreAspect && !this.props.Document.fillColumn ? layoutDoc[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
- const height = () => this.getDocHeight(layoutDoc);
+ const width = () => Math.min(d._nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
+ const height = () => this.getDocHeight(d);
const dref = React.createRef<HTMLDivElement>();
- const dxf = () => this.getDocTransform(layoutDoc, dref.current!);
+ const dxf = () => this.getDocTransform(d, dref.current!);
this._docXfs.push({ dxf: dxf, width: width, height: height });
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
const style = this.isStackingView ? { width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` };
return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
- {this.getDisplayDoc(pair.layout || d, pair.data, dxf, width)}
+ {this.getDisplayDoc(d, Cast(d.resolvedDataDoc, Doc, null) || this.props.DataDoc, dxf, width)}
</div>;
});
}
@@ -109,13 +104,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
componentDidMount() {
super.componentDidMount();
this._heightDisposer = reaction(() => {
- if (this.props.Document.autoHeight) {
+ if (this.props.Document._autoHeight) {
const sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]);
if (this.isStackingView) {
const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => {
const r1 = Math.max(maxHght,
(this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => {
- const val = height + this.childDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
+ const val = height + this.getDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
return val;
}, this.yMargin));
return r1;
@@ -130,7 +125,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
},
(hgt: number) => {
const doc = hgt === -1 ? undefined : this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
- doc && hgt > 0 && (Doc.Layout(doc).height = hgt);
+ doc && hgt > 0 && (Doc.Layout(doc)._height = hgt);
},
{ fireImmediately: true }
);
@@ -153,11 +148,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
createRef = (ele: HTMLDivElement | null) => {
this._masonryGridRef = ele;
- this.createDropAndGestureTarget(ele!); //so the whole grid is the drop target?
- }
-
- overlays = (doc: Doc) => {
- return doc.type === DocumentType.IMG || doc.type === DocumentType.VID ? { title: StrCast(this.props.Document.showTitles), caption: StrCast(this.props.Document.showCaptions) } : {};
+ this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
}
@computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
@@ -170,9 +161,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
Document={doc}
DataDocument={dataDoc}
LibraryPath={this.props.LibraryPath}
- showOverlays={this.overlays}
renderDepth={this.props.renderDepth + 1}
- ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
fitToBox={this.props.fitToBox}
onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
PanelWidth={width}
@@ -187,24 +176,22 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
active={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- setPreviewScript={emptyFunction}
- previewScript={undefined}>
+ pinToPres={this.props.pinToPres}>
</ContentFittingDocumentView>;
}
getDocHeight(d?: Doc) {
if (!d) return 0;
const layoutDoc = Doc.Layout(d);
- const nw = NumCast(layoutDoc.nativeWidth);
- const nh = NumCast(layoutDoc.nativeHeight);
+ const nw = NumCast(layoutDoc._nativeWidth);
+ const nh = NumCast(layoutDoc._nativeHeight);
let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
- if (!layoutDoc.ignoreAspect && !layoutDoc.fitWidth && nw && nh) {
+ if (!layoutDoc.ignoreAspect && !layoutDoc._fitWidth && nw && nh) {
const aspect = nw && nh ? nh / nw : 1;
- if (!(d.nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
+ if (!(d._nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
return wid * aspect;
}
- return layoutDoc.fitWidth ? !layoutDoc.nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin :
- Math.min(wid * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc.nativeHeight)) / NumCast(layoutDoc.nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym]();
+ return layoutDoc._fitWidth ? !layoutDoc._nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin :
+ Math.min(wid * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym]();
}
columnDividerDown = (e: React.PointerEvent) => {
@@ -250,7 +237,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
targInd = i;
- const axis = this.Document.viewType === CollectionViewType.Masonry ? 0 : 1;
+ const axis = this.Document._viewType === CollectionViewType.Masonry ? 0 : 1;
plusOne = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0;
}
});
@@ -296,7 +283,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.sectionFilter;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
- const types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
+ const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
type = types[0];
}
@@ -311,7 +298,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
docList={docList}
parent={this}
type={type}
- createDropTarget={this.createDropAndGestureTarget}
+ createDropTarget={this.createDashEventsTarget}
screenToLocalTransform={this.props.ScreenToLocalTransform}
/>;
}
@@ -321,15 +308,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const y = this._scroll; // required for document decorations to update when the text box container is scrolled
const { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
const outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
+ const scaling = 1 / Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]());
const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
- return this.props.ScreenToLocalTransform().
- translate(offset[0], offset[1] + (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0));
+ const offsetx = (doc[WidthSym]() - doc[WidthSym]() / scaling) / 2;
+ const offsety = (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0);
+ return this.props.ScreenToLocalTransform().translate(offset[0] - offsetx, offset[1] + offsety).scale(scaling);
}
sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.sectionFilter;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
- const types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
+ const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
type = types[0];
}
@@ -344,7 +333,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
docList={docList}
parent={this}
type={type}
- createDropTarget={this.createDropAndGestureTarget}
+ createDropTarget={this.createDashEventsTarget}
screenToLocalTransform={this.props.ScreenToLocalTransform}
setDocHeight={this.setDocHeight}
/>;
@@ -367,7 +356,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
onToggle = (checked: Boolean) => {
- this.props.Document.chromeStatus = checked ? "collapsed" : "view-mode";
+ this.props.Document._chromeStatus = checked ? "collapsed" : "view-mode";
}
onContextMenu = (e: React.MouseEvent): void => {
@@ -378,11 +367,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
subItems.push({ description: `${this.props.Document.showTitles ? "Hide Titles" : "Show Titles"}`, event: () => this.props.Document.showTitles = !this.props.Document.showTitles ? "title" : "", icon: "plus" });
subItems.push({ description: `${this.props.Document.showCaptions ? "Hide Captions" : "Show Captions"}`, event: () => this.props.Document.showCaptions = !this.props.Document.showCaptions ? "caption" : "", icon: "plus" });
ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" });
-
- const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
- const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
- onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) });
- !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
}
@@ -406,20 +390,26 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
<div className="collectionStackingMasonry-cont" >
<div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
ref={this.createRef}
+ style={{
+ overflowY: this.props.active() ? "auto" : "hidden",
+ transform: `scale(${Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]())})`,
+ height: `${Math.max(100, 100 * 1 / Math.min(this.props.PanelWidth() / this.layoutDoc[WidthSym](), this.props.PanelHeight() / this.layoutDoc[HeightSym]()))}%`,
+ transformOrigin: "top"
+ }}
onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
onDrop={this.onDrop.bind(this)}
onContextMenu={this.onContextMenu}
- onWheel={e => e.stopPropagation()} >
+ onWheel={e => this.props.active() && 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.props.Document.chromeStatus !== 'disabled' ? <Switch
+ {this.props.Document._chromeStatus !== 'disabled' ? <Switch
onChange={this.onToggle}
onClick={this.onToggle}
- defaultChecked={this.props.Document.chromeStatus !== 'view-mode'}
+ defaultChecked={this.props.Document._chromeStatus !== 'view-mode'}
checkedChildren="edit"
unCheckedChildren="view"
/> : null}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 65c4b3195..c4680fc28 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -4,7 +4,7 @@ import { faPalette } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import { ScriptField } from "../../../new_fields/ScriptField";
import { NumCast, StrCast } from "../../../new_fields/Types";
@@ -18,6 +18,12 @@ import { EditableView } from "../EditableView";
import { CollectionStackingView } from "./CollectionStackingView";
import "./CollectionStackingView.scss";
import { TraceMobx } from "../../../new_fields/util";
+import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { ImageField } from "../../../new_fields/URLField";
+import { ImageBox } from "../nodes/ImageBox";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { RichTextField } from "../../../new_fields/RichTextField";
library.add(faPalette);
@@ -133,9 +139,10 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action
addDocument = (value: string, shiftDown?: boolean) => {
+ if (!value) return false;
this._createAliasSelected = false;
const key = StrCast(this.props.parent.props.Document.sectionFilter);
- const newDoc = Docs.Create.TextDocument({ height: 18, width: 200, documentText: "@@@" + value, title: value, autoHeight: true });
+ const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, title: value, _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;
@@ -258,6 +265,57 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@observable _headingsHack: number = 1;
+ menuCallback = (x: number, y: number) => {
+ ContextMenu.Instance.clearItems();
+ const layoutItems: ContextMenuProps[] = [];
+ const docItems: ContextMenuProps[] = [];
+
+ const dataDoc = this.props.parent.props.DataDoc || this.props.parent.Document;
+ Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey =>
+ docItems.push({
+ description: ":" + fieldKey, event: () => {
+ const created = Docs.Get.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.parent.props.Document));
+ if (created) {
+ if (this.props.parent.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document);
+ }
+ return this.props.parent.props.addDocument(created);
+ }
+ }, icon: "compress-arrows-alt"
+ }));
+ Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => DocListCast(dataDoc[fieldKey]).length).map(fieldKey =>
+ docItems.push({
+ description: ":" + fieldKey, event: () => {
+ const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
+ if (created) {
+ if (this.props.parent.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document);
+ }
+ return this.props.parent.props.addDocument(created);
+ }
+ }, icon: "compress-arrows-alt"
+ }));
+ layoutItems.push({ description: ":freeform", event: () => this.props.parent.props.addDocument(Docs.Create.FreeformDocument([], { _width: 200, _height: 200, _LODdisable: true })), icon: "compress-arrows-alt" });
+ layoutItems.push({ description: ":carousel", event: () => this.props.parent.props.addDocument(Docs.Create.CarouselDocument([], { _width: 400, _height: 200, _LODdisable: true })), icon: "compress-arrows-alt" });
+ layoutItems.push({ description: ":columns", event: () => this.props.parent.props.addDocument(Docs.Create.MulticolumnDocument([], { _width: 200, _height: 200 })), icon: "compress-arrows-alt" });
+ layoutItems.push({ description: ":image", event: () => this.props.parent.props.addDocument(Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { _width: 200, _height: 200 })), icon: "compress-arrows-alt" });
+
+ ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" });
+ ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
+ ContextMenu.Instance.setDefaultItem("::", (name: string): void => {
+ Doc.GetProto(this.props.parent.props.Document)[name] = "";
+ const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true });
+ if (created) {
+ if (this.props.parent.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document);
+ }
+ this.props.parent.props.addDocument(created);
+ }
+ });
+ const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y);
+ ContextMenu.Instance.displayMenu(pt[0], pt[1]);
+ }
+
render() {
TraceMobx();
const cols = this.props.cols();
@@ -293,7 +351,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
style={{
width: (style.columnWidth) /
((uniqueHeadings.length +
- ((this.props.parent.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.Document.chromeStatus !== 'disabled') ? 1 : 0)) || 1)
+ ((this.props.parent.props.Document._chromeStatus !== 'view-mode' && this.props.parent.props.Document._chromeStatus !== 'disabled') ? 1 : 0)) || 1)
}}>
<div className={"collectionStackingView-collapseBar" + (this.props.headingObject.collapsed === true ? " active" : "")} onClick={this.collapseSection}></div>
{/* the default bucket (no key value) has a tooltip that describes what it is.
@@ -333,7 +391,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
</div>
</div> : (null);
for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `;
- const chromeStatus = this.props.parent.props.Document.chromeStatus;
+ const chromeStatus = this.props.parent.props.Document._chromeStatus;
return (
<div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }}
ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
@@ -358,7 +416,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
{(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ?
<div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
style={{ width: style.columnWidth / style.numGroupColumns }}>
- <EditableView {...newEditableViewProps} />
+ <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} />
</div> : null}
</div>
}
diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx
index 105061f46..8c7e113b2 100644
--- a/src/client/views/collections/CollectionStaffView.tsx
+++ b/src/client/views/collections/CollectionStaffView.tsx
@@ -23,10 +23,6 @@ export class CollectionStaffView extends CollectionSubView(doc => doc) {
this.props.Document.staves = 5;
}
- @computed get fieldExtensionDoc() {
- return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
- }
-
@computed get addStaffButton() {
return <div onPointerDown={this.addStaff}>+</div>;
}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index b5067ea9e..f125403f9 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, reaction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, trace } from "mobx";
import * as rp from 'request-promise';
import CursorField from "../../../new_fields/CursorField";
import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
@@ -24,6 +24,7 @@ import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
import { Networking } from "../../Network";
import { GestureUtils } from "../../../pen-gestures/GestureUtils";
+import { InteractionUtils } from "../../util/InteractionUtils";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc) => boolean;
@@ -39,7 +40,6 @@ export interface CollectionViewProps extends FieldViewProps {
export interface SubCollectionViewProps extends CollectionViewProps {
CollectionView: Opt<CollectionView>;
- ruleProvider: Doc | undefined;
children?: never | (() => JSX.Element[]) | React.ReactNode;
isAnnotationOverlay?: boolean;
annotationsKey: string;
@@ -49,48 +49,57 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) {
private dropDisposer?: DragManager.DragDropDisposer;
private gestureDisposer?: GestureUtils.GestureEventDisposer;
+ protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
private _childLayoutDisposer?: IReactionDisposer;
- protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
- this.dropDisposer && this.dropDisposer();
- this.gestureDisposer && this.gestureDisposer();
+ protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ this.dropDisposer ?.();
+ this.gestureDisposer ?.();
+ this.multiTouchDisposer ?.();
if (ele) {
this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
+ this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
}
}
protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
- this.createDropAndGestureTarget(ele);
+ this.createDashEventsTarget(ele);
}
componentDidMount() {
- this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)],
- async (args) => {
- if (args[1] instanceof Doc) {
- this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc), "layoutFromParent"));
+ this._childLayoutDisposer = reaction(() => [this.childDocs, (Cast(this.props.Document.childLayout, Doc) as Doc) ?.[Id]],
+ (args) => {
+ const childLayout = Cast(this.props.Document.childLayout, Doc);
+ if (childLayout instanceof Doc) {
+ this.childDocs.map(doc => Doc.ApplyTemplateTo(childLayout, doc, "layout_fromParent"));
}
- else if (!(args[1] instanceof Promise)) {
- this.childDocs.filter(d => !d.isTemplateField).map(async doc => doc.layoutKey === "layoutFromParent" && (doc.layoutKey = "layout"));
+ else if (!(childLayout instanceof Promise)) {
+ this.childDocs.filter(d => !d.isTemplateForField).map(doc => doc.layoutKey === "layout_fromParent" && (doc.layoutKey = "layout"));
}
- });
+ }, { fireImmediately: true });
}
componentWillUnmount() {
this._childLayoutDisposer && this._childLayoutDisposer();
}
- @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
- @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
+ @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
// The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
// When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
// 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() {
- return this.props.annotationsKey ? (this.extensionDoc ? this.extensionDoc[this.props.annotationsKey] : undefined) : this.dataDoc[this.props.fieldKey];
+ const { annotationsKey, fieldKey } = this.props;
+ if (annotationsKey) {
+ return this.dataDoc[fieldKey + "-" + annotationsKey];
+ }
+ return this.dataDoc[fieldKey];
}
- get childLayoutPairs() {
- return this.childDocs.map(cd => Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, cd)).filter(pair => pair.layout).map(pair => ({ layout: pair.layout!, data: pair.data! }));
+ get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
+ const { Document, DataDoc } = this.props;
+ const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, doc)).filter(pair => pair.layout);
+ return validPairs.map(({ data, layout }) => ({ data: data!, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
get childDocList() {
return Cast(this.dataField, listSpec(Doc));
@@ -98,7 +107,34 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
get childDocs() {
const docs = DocListCast(this.dataField);
const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
- return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+ const viewedDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+ const docFilters = Cast(this.props.Document._docFilter, listSpec("string"), []);
+ const clusters: { [key: string]: { [value: string]: string } } = {};
+ for (let i = 0; i < docFilters.length; i += 3) {
+ const [key, value, modifiers] = docFilters.slice(i, i + 3);
+ const cluster = clusters[key];
+ if (!cluster) {
+ const child: { [value: string]: string } = {};
+ child[value] = modifiers;
+ clusters[key] = child;
+ } else {
+ cluster[value] = modifiers;
+ }
+ }
+ const filteredDocs = docFilters.length ? viewedDocs.filter(d => {
+ for (const key of Object.keys(clusters)) {
+ const cluster = clusters[key];
+ const satisfiesFacet = Object.keys(cluster).some(inner => {
+ const modifier = cluster[inner];
+ return (modifier === "x") !== Doc.matchFieldValue(d, key, inner);
+ });
+ if (!satisfiesFacet) {
+ return false;
+ }
+ }
+ return true;
+ }) : viewedDocs;
+ return filteredDocs;
}
@action
@@ -146,7 +182,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
if (docDragData && !docDragData.applyAsTemplate) {
if (de.altKey && docDragData.draggedDocuments.length) {
this.childDocs.map(doc =>
- Doc.ApplyTemplateTo(docDragData.draggedDocuments[0], doc, "layoutFromParent"));
+ Doc.ApplyTemplateTo(docDragData.draggedDocuments[0], doc, "layout_fromParent"));
e.stopPropagation();
return true;
}
@@ -157,7 +193,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
const movedDocs = docDragData.draggedDocuments;
added = movedDocs.reduce((added: boolean, d, i) =>
docDragData.droppedDocuments[i] !== d ? this.props.addDocument(docDragData.droppedDocuments[i]) :
- docDragData.moveDocument?.(d, this.props.Document, this.props.addDocument) || added, false);
+ docDragData.moveDocument ?.(d, this.props.Document, this.props.addDocument) || added, false);
} else {
added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
}
@@ -203,7 +239,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
this.props.addDocument && this.props.addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
}
} else if (text) {
- this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text }));
+ this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
}
return;
}
@@ -213,8 +249,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
if (img) {
const split = img.split("src=\"")[1].split("\"")[0];
- console.log("creating image", split);
- const doc = Docs.Create.ImageDocument(split, { ...options, width: 300 });
+ const doc = Docs.Create.ImageDocument(split, { ...options, _width: 300 });
ImageUtils.ExtractExif(doc);
this.props.addDocument(doc);
return;
@@ -229,7 +264,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
});
} else {
- const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", width: 300, height: 300, documentText: text });
+ const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300, documentText: text });
this.props.addDocument(htmlDoc);
}
return;
@@ -237,12 +272,12 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
if (text && text.indexOf("www.youtube.com/watch") !== -1) {
const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
- this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, width: 400, height: 315, nativeWidth: 600, nativeHeight: 472.5 }));
+ this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, _width: 400, _height: 315, _nativeWidth: 600, _nativeHeight: 472.5 }));
return;
}
let matches: RegExpExecArray | null;
if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
- const newBox = Docs.Create.TextDocument({ ...options, width: 400, height: 200, title: "Awaiting title from Google Docs..." });
+ const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." });
const proto = newBox.proto!;
const documentId = matches[2];
proto[GoogleRef] = documentId;
@@ -289,13 +324,20 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
formData.append('file', file);
const dropFileName = file ? file.name : "-empty-";
- promises.push(Networking.PostFormDataToServer("/upload", formData).then(results => {
- results.map(action(({ clientAccessPath }: any) => {
- const full = { ...options, width: 300, title: dropFileName };
+ promises.push(Networking.PostFormDataToServer("/uploadFormData", formData).then(results => {
+ results.map(action((result: any) => {
+ const { clientAccessPath, nativeWidth, nativeHeight, contentSize } = result;
+ const full = { ...options, _width: 300, title: dropFileName };
const pathname = Utils.prepend(clientAccessPath);
Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
- doc && (Doc.GetProto(doc).fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""));
- doc && this.props.addDocument(doc);
+ if (doc) {
+ const proto = Doc.GetProto(doc);
+ proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
+ nativeWidth && (proto["data-nativeWidth"] = nativeWidth);
+ nativeHeight && (proto["data-nativeHeight"] = nativeHeight);
+ contentSize && (proto.contentSize = contentSize);
+ this.props.addDocument(doc);
+ }
});
}));
}));
@@ -306,7 +348,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
} else {
if (text && !text.includes("https://")) {
- this.props.addDocument(Docs.Create.TextDocument({ ...options, documentText: "@@@" + text, width: 400, height: 315 }));
+ this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
}
batch.end();
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 2b13d87ee..a7733ab5f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,19 +1,22 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faAngleRight, faArrowsAltH, faBell, faCamera, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faExpand, faMinus, faPlus, faTrash, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked, runInAction } from "mobx";
+import { action, computed, observable, runInAction, untracked } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, Field, HeightSym, WidthSym } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
import { Document, listSpec } from '../../../new_fields/Schema';
import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
-import { emptyFunction, Utils, returnFalse, emptyPath } from '../../../Utils';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { emptyFunction, emptyPath, returnFalse, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
+import { makeTemplate } from '../../util/DropConverter';
+import { Scripting } from '../../util/Scripting';
import { SelectionManager } from '../../util/SelectionManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
@@ -21,13 +24,17 @@ import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from "../EditableView";
import { MainView } from '../MainView';
+import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
+import { ImageBox } from '../nodes/ImageBox';
import { KeyValueBox } from '../nodes/KeyValueBox';
+import { ScriptBox } from '../ScriptBox';
import { Templates } from '../Templates';
-import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import React = require("react");
-import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { CollectionViewType } from './CollectionView';
+import { RichTextField } from '../../../new_fields/RichTextField';
+import { ObjectField } from '../../../new_fields/ObjectField';
export interface TreeViewProps {
@@ -38,7 +45,6 @@ export interface TreeViewProps {
prevSibling?: Doc;
renderDepth: number;
deleteDoc: (doc: Doc) => boolean;
- ruleProvider: Doc | undefined;
moveDocument: DragManager.MoveFunction;
dropAction: "alias" | "copy" | undefined;
addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string, libraryPath?: Doc[]) => boolean;
@@ -51,12 +57,13 @@ export interface TreeViewProps {
outdentDocument?: () => void;
ScreenToLocalTransform: () => Transform;
outerXf: () => { translateX: number, translateY: number };
- treeViewId: string;
+ treeViewId: Doc;
parentKey: string;
active: (outsideReaction?: boolean) => boolean;
hideHeaderFields: () => boolean;
preventTreeViewOpen: boolean;
renderedIds: string[];
+ onCheckedClick?: ScriptField;
}
library.add(faTrashAlt);
@@ -173,8 +180,8 @@ class TreeView extends React.Component<TreeViewProps> {
SetValue={undoBatch((value: string) => Doc.SetInPlace(this.props.document, key, value, false) || true)}
OnFillDown={undoBatch((value: string) => {
Doc.SetInPlace(this.props.document, key, value, false);
- const layoutDoc = this.props.document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.layoutCustom)) : undefined;
- const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ const layoutDoc = this.props.document.layout_custom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.layout_custom)) : undefined;
+ const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
TreeView.loadId = doc[Id];
return this.props.addDocument(doc);
})}
@@ -205,7 +212,7 @@ class TreeView extends React.Component<TreeViewProps> {
ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
ContextMenu.Instance.addItem({ description: "Create New Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" });
}
- ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { const kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
+ ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { const kvp = Docs.Create.KVPDocument(this.props.document, { _width: 300, _height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" });
ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15);
e.stopPropagation();
@@ -232,8 +239,8 @@ class TreeView extends React.Component<TreeViewProps> {
if (inside) {
addDoc = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) || addDoc(doc);
}
- const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments);
- return ((de.complete.docDragData.dropAction && (de.complete.docDragData.treeViewId !== this.props.treeViewId)) || de.complete.docDragData.userDropAction) ?
+ const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId[Id] ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments);
+ return ((de.complete.docDragData.dropAction && (de.complete.docDragData.treeViewId !== this.props.treeViewId[Id])) || de.complete.docDragData.userDropAction) ?
de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d) || added, false)
: de.complete.docDragData.moveDocument ?
movedDocs.reduce((added, d) => de.complete.docDragData?.moveDocument?.(d, undefined, addDoc) || added, false)
@@ -251,21 +258,21 @@ class TreeView extends React.Component<TreeViewProps> {
}
docWidth = () => {
const layoutDoc = Doc.Layout(this.props.document);
- const aspect = NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth);
+ const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth);
if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20));
- return NumCast(layoutDoc.nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20;
+ return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20;
}
docHeight = () => {
const layoutDoc = Doc.Layout(this.props.document);
const bounds = this.boundsOfCollectionDocument;
return Math.min(this.MAX_EMBED_HEIGHT, (() => {
- const aspect = NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth, 1);
+ const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth, 1);
if (aspect) return this.docWidth() * aspect;
if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x);
- return layoutDoc.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) :
- Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc.nativeHeight)) / NumCast(layoutDoc.nativeWidth,
- NumCast(this.props.containingCollection.height)))) :
- NumCast(layoutDoc.height) ? NumCast(layoutDoc.height) : 50;
+ return layoutDoc._fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection._height) :
+ Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth,
+ NumCast(this.props.containingCollection._height)))) :
+ NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50;
})());
}
@@ -286,7 +293,7 @@ class TreeView extends React.Component<TreeViewProps> {
DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.hideHeaderFields, this.props.preventTreeViewOpen,
- [...this.props.renderedIds, doc[Id]], this.props.libraryPath);
+ [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick);
} else {
contentElement = <EditableView
key="editableView"
@@ -317,8 +324,6 @@ class TreeView extends React.Component<TreeViewProps> {
return rows;
}
- noOverlays = (doc: Doc) => ({ title: "", caption: "" });
-
@computed get renderContent() {
const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
if (expandKey !== undefined) {
@@ -331,7 +336,7 @@ class TreeView extends React.Component<TreeViewProps> {
this.templateDataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.hideHeaderFields, this.props.preventTreeViewOpen,
- [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath)}
+ [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick)}
</ul >;
} else if (this.treeViewExpandedView === "fields") {
return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
@@ -345,8 +350,6 @@ class TreeView extends React.Component<TreeViewProps> {
DataDocument={this.templateDataDoc}
LibraryPath={emptyPath}
renderDepth={this.props.renderDepth + 1}
- showOverlays={this.noOverlays}
- ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider}
fitToBox={this.boundsOfCollectionDocument !== undefined}
PanelWidth={this.docWidth}
PanelHeight={this.docHeight}
@@ -359,16 +362,32 @@ class TreeView extends React.Component<TreeViewProps> {
active={this.props.active}
whenActiveChanged={emptyFunction}
addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- setPreviewScript={emptyFunction} />
+ pinToPres={this.props.pinToPres} />
</div>;
}
}
+ @action
+ bulletClick = (e: React.MouseEvent) => {
+ if (this.props.onCheckedClick && this.props.document.type !== DocumentType.COL) {
+ // this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check";
+ ScriptCast(this.props.onCheckedClick).script.run({
+ this: this.props.document.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.props.document,
+ heading: this.props.containingCollection.title,
+ checked: this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check",
+ containingTreeView: this.props.treeViewId,
+ }, console.log);
+ } else {
+ this.treeViewOpen = !this.treeViewOpen;
+ }
+ e.stopPropagation();
+ }
+
@computed
get renderBullet() {
- return <div className="bullet" title="view inline" onClick={action((e: React.MouseEvent) => { this.treeViewOpen = !this.treeViewOpen; e.stopPropagation(); })} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
- {<FontAwesomeIcon icon={!this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
+ const checked = this.props.document.type === DocumentType.COL ? undefined : this.props.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined;
+ return <div className="bullet" title="view inline" onClick={this.bulletClick} style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "black"), opacity: 0.4 }}>
+ {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />}
</div>;
}
/**
@@ -377,7 +396,7 @@ class TreeView extends React.Component<TreeViewProps> {
@computed
get renderTitle() {
const reference = React.createRef<HTMLDivElement>();
- const onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId, true);
+ const onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true);
const headerElements = (
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
@@ -427,7 +446,7 @@ class TreeView extends React.Component<TreeViewProps> {
}
public static GetChildElements(
childDocs: Doc[],
- treeViewId: string,
+ treeViewId: Doc,
containingCollection: Doc,
dataDoc: Doc | undefined,
key: string,
@@ -448,7 +467,8 @@ class TreeView extends React.Component<TreeViewProps> {
hideHeaderFields: () => boolean,
preventTreeViewOpen: boolean,
renderedIds: string[],
- libraryPath: Doc[] | undefined
+ libraryPath: Doc[] | undefined,
+ onCheckedClick: ScriptField | undefined
) {
const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
@@ -496,7 +516,7 @@ class TreeView extends React.Component<TreeViewProps> {
const rowWidth = () => panelWidth() - 20;
return docs.map((child, i) => {
- const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child);
+ const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, child);
if (!pair.layout || pair.data instanceof Promise) {
return (null);
}
@@ -526,7 +546,7 @@ class TreeView extends React.Component<TreeViewProps> {
};
const childLayout = Doc.Layout(pair.layout);
const rowHeight = () => {
- const aspect = NumCast(childLayout.nativeWidth, 0) / NumCast(childLayout.nativeHeight, 0);
+ const aspect = NumCast(childLayout._nativeWidth, 0) / NumCast(childLayout._nativeHeight, 0);
return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]();
};
return !(child instanceof Doc) ? (null) : <TreeView
@@ -536,10 +556,10 @@ class TreeView extends React.Component<TreeViewProps> {
containingCollection={containingCollection}
prevSibling={docs[i]}
treeViewId={treeViewId}
- ruleProvider={containingCollection.isRuleProvider && pair.layout.type !== DocumentType.TEXT ? containingCollection : containingCollection.ruleProvider as Doc}
key={child[Id]}
indentDocument={indent}
outdentDocument={outdent}
+ onCheckedClick={onCheckedClick}
renderDepth={renderDepth}
deleteDoc={remove}
addDocument={addDocument}
@@ -608,6 +628,49 @@ export class CollectionTreeView extends CollectionSubView(Document) {
layoutItems.push({ description: (this.props.Document.hideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.hideHeaderFields = !this.props.Document.hideHeaderFields, icon: "paint-brush" });
ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
}
+ ContextMenu.Instance.addItem({
+ description: "Buxton Layout", icon: "eye", event: () => {
+ DocListCast(this.dataDoc[this.props.fieldKey]).map(d => {
+ DocListCast(d.data).map((img, i) => {
+ const caption = (d.captions as any)[i]?.data;
+ if (caption instanceof ObjectField) {
+ Doc.GetProto(img).caption = ObjectField.MakeCopy(caption as ObjectField);
+ }
+ img._hideSidebar = true;
+ d.captions = undefined;
+ });
+ });
+ const { TextDocument, ImageDocument, CarouselDocument } = Docs.Create;
+ const { Document } = this.props;
+ const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif";
+ const detailedTemplate = `{ "doc": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "short_description" } } ] }, { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "year" } } ] }, { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "company" } } ] } ] }, "selection":{"type":"text","anchor":1,"head":1},"storedMarks":[] }`;
+
+ const textDoc = TextDocument("", { title: "details", _autoHeight: true });
+ const detailedLayout = Docs.Create.StackingDocument([
+ CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" }),
+ textDoc,
+ ], { _chromeStatus: "disabled", title: "detailed layout stack" });
+ textDoc.data = new RichTextField(detailedTemplate, "short_description year company");
+ detailedLayout.isTemplateDoc = makeTemplate(detailedLayout);
+
+ const cardLayout = ImageDocument(fallbackImg, { title: "cardLayout", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work?
+ cardLayout.proto!.layout = ImageBox.LayoutString("hero");
+ cardLayout.showTitle = "title";
+ cardLayout.showTitleHover = "titlehover";
+
+ Document.childLayout = cardLayout;
+ Document.childDetailed = detailedLayout;
+ Document._viewType = CollectionViewType.Pivot;
+ Document.pivotField = "company";
+ }
+ });
+ const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+ const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+ onClicks.push({
+ description: "Edit onChecked Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Checked Changed ...", this.props.Document,
+ "onCheckedClick", obj.x, obj.y, { heading: "boolean", checked: "boolean", treeViewContainer: Doc.name })
+ });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});
@@ -622,17 +685,17 @@ export class CollectionTreeView extends CollectionSubView(Document) {
}
render() {
- const dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
+ const dropAction = StrCast(this.props.Document._dropAction) as dropActionType;
const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, false);
const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
return !this.childDocs ? (null) : (
<div className="collectionTreeView-dropTarget" id="body"
- style={{ background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document.yMargin, 20)}px` }}
+ style={{ background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document._yMargin, 20)}px` }}
onContextMenu={this.onContextMenu}
onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
onDrop={this.onTreeDrop}
ref={this.createTreeDropTarget}>
- <EditableView
+ {(this.props.Document.treeViewHideTitle ? (null) : <EditableView
contents={this.dataDoc.title}
display={"block"}
maxHeight={72}
@@ -641,21 +704,47 @@ export class CollectionTreeView extends CollectionSubView(Document) {
SetValue={undoBatch((value: string) => Doc.SetInPlace(this.dataDoc, "title", value, false) || true)}
OnFillDown={undoBatch((value: string) => {
Doc.SetInPlace(this.dataDoc, "title", value, false);
- const layoutDoc = this.props.Document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.layoutCustom)) : undefined;
- const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ const layoutDoc = this.props.Document.layout_custom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.layout_custom)) : undefined;
+ const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
TreeView.loadId = doc[Id];
Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, false);
- })} />
+ })} />)}
{this.props.Document.allowClear ? this.renderClearButton : (null)}
<ul className="no-indent" style={{ width: "max-content" }} >
{
- TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
+ TreeView.GetChildElements(this.childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.hideHeaderFields),
- BoolCast(this.props.Document.preventTreeViewOpen), [], this.props.LibraryPath)
+ BoolCast(this.props.Document.preventTreeViewOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick))
}
</ul>
</div >
);
}
-} \ No newline at end of file
+}
+
+Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey: string, facetHeader: string) {
+ const allCollectionDocs = DocListCast(dataDoc[dataKey]);
+ const facetValues = Array.from(allCollectionDocs.reduce((set, child) =>
+ set.add(Field.toString(child[facetHeader] as Field)), new Set<string>()));
+
+ const facetValueDocSet = facetValues.sort().map(facetValue =>
+ Docs.Create.TextDocument("", {
+ title: facetValue.toString(),
+ treeViewChecked: ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)",
+ { layoutDoc: Doc.name, facetHeader: "string", facetValue: "string" },
+ { layoutDoc, facetHeader, facetValue })
+ }));
+ return new List<Doc>(facetValueDocSet);
+});
+
+Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) {
+ const docFilters = Cast(layoutDoc._docFilter, listSpec("string"), []);
+ for (let i = 0; i < docFilters.length; i += 3) {
+ const [header, value, state] = docFilters.slice(i, i + 3);
+ if (header === facetHeader && value === facetValue) {
+ return state;
+ }
+ }
+ return undefined;
+}); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 1d399e26f..ab6ac0eaf 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,39 +1,42 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEye } from '@fortawesome/free-regular-svg-icons';
import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
-import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, IReactionDisposer, observable, reaction, runInAction, computed } from 'mobx';
import { observer } from "mobx-react";
import * as React from 'react';
+import Lightbox from 'react-image-lightbox-with-rotate';
+import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
+import { DateField } from '../../../new_fields/DateField';
+import { Doc, DocListCast } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
-import { StrCast, BoolCast, Cast } from '../../../new_fields/Types';
+import { listSpec } from '../../../new_fields/Schema';
+import { BoolCast, Cast, StrCast, NumCast } from '../../../new_fields/Types';
+import { ImageField } from '../../../new_fields/URLField';
+import { TraceMobx } from '../../../new_fields/util';
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { Utils } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
+import { ImageUtils } from '../../util/Import & Export/ImageUtils';
+import { SelectionManager } from '../../util/SelectionManager';
import { ContextMenu } from "../ContextMenu";
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { ScriptBox } from '../ScriptBox';
+import { Touchable } from '../Touchable';
import { CollectionDockingView } from "./CollectionDockingView";
import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionCarouselView } from './CollectionCarouselView';
+import { CollectionLinearView } from './CollectionLinearView';
+import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
+import { CollectionPivotView } from './CollectionPivotView';
import { CollectionSchemaView } from "./CollectionSchemaView";
import { CollectionStackingView } from './CollectionStackingView';
+import { CollectionStaffView } from './CollectionStaffView';
import { CollectionTreeView } from "./CollectionTreeView";
+import './CollectionView.scss';
import { CollectionViewBaseChrome } from './CollectionViewChromes';
-import { ImageUtils } from '../../util/Import & Export/ImageUtils';
-import { CollectionLinearView } from '../CollectionLinearView';
-import { CollectionStaffView } from './CollectionStaffView';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { ImageField } from '../../../new_fields/URLField';
-import { DocListCast } from '../../../new_fields/Doc';
-import Lightbox from 'react-image-lightbox-with-rotate';
-import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
export const COLLECTION_BORDER_WIDTH = 2;
-import { DateField } from '../../../new_fields/DateField';
-import { Doc, } from '../../../new_fields/Doc';
-import { listSpec } from '../../../new_fields/Schema';
-import { DocumentManager } from '../../util/DocumentManager';
-import { SelectionManager } from '../../util/SelectionManager';
-import './CollectionView.scss';
-import { FieldViewProps, FieldView } from '../nodes/FieldView';
-import { Touchable } from '../Touchable';
-import { TraceMobx } from '../../../new_fields/util';
-import { Utils } from '../../../Utils';
const path = require('path');
library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -45,9 +48,12 @@ export enum CollectionViewType {
Tree,
Stacking,
Masonry,
+ Multicolumn,
Pivot,
+ Carousel,
Linear,
- Staff
+ Staff,
+ Timeline
}
export namespace CollectionViewType {
@@ -59,8 +65,10 @@ export namespace CollectionViewType {
["tree", CollectionViewType.Tree],
["stacking", CollectionViewType.Stacking],
["masonry", CollectionViewType.Masonry],
+ ["multicolumn", CollectionViewType.Multicolumn],
["pivot", CollectionViewType.Pivot],
- ["linear", CollectionViewType.Linear]
+ ["carousel", CollectionViewType.Carousel],
+ ["linear", CollectionViewType.Linear],
]);
export const valueOf = (value: string) => stringMapping.get(value.toLowerCase());
@@ -87,7 +95,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
get collectionViewType(): CollectionViewType | undefined {
- const viewField = Cast(this.props.Document.viewType, "number");
+ const viewField = NumCast(this.props.Document._viewType);
if (CollectionView._safeMode) {
if (viewField === CollectionViewType.Freeform) {
return CollectionViewType.Tree;
@@ -96,15 +104,15 @@ export class CollectionView extends Touchable<FieldViewProps> {
return CollectionViewType.Freeform;
}
}
- return viewField === undefined ? CollectionViewType.Invalid : viewField;
+ return viewField;
}
componentDidMount = () => {
- this._reactionDisposer = reaction(() => StrCast(this.props.Document.chromeStatus),
+ this._reactionDisposer = reaction(() => StrCast(this.props.Document._chromeStatus),
() => {
// chrome status is one of disabled, collapsed, or visible. this determines initial state from document
// chrome status may also be view-mode, in reference to stacking view's toggle mode. it is essentially disabled mode, but prevents the toggle button from showing up on the left sidebar.
- const chromeStatus = this.props.Document.chromeStatus;
+ const chromeStatus = this.props.Document._chromeStatus;
if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed")) {
runInAction(() => this._collapsed = true);
}
@@ -114,7 +122,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
componentWillUnmount = () => this._reactionDisposer && this._reactionDisposer();
// bcz: Argh? What's the height of the collection chromes??
- chromeHeight = () => (this.props.Document.chromeStatus === "enabled" ? -60 : 0);
+ chromeHeight = () => (this.props.Document._chromeStatus === "enabled" ? -60 : 0);
active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0;
@@ -124,8 +132,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
addDocument(doc: Doc): boolean {
const targetDataDoc = Doc.GetProto(this.props.Document);
Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
- const extension = Doc.fieldExtensionDoc(targetDataDoc, this.props.fieldKey); // set metadata about the field being rendered (ie, the set of documents) on an extension field for that field
- extension && (extension.lastModified = new DateField(new Date(Date.now())));
+ targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
Doc.GetProto(doc).lastOpened = new DateField;
return true;
}
@@ -172,24 +179,26 @@ export class CollectionView extends Touchable<FieldViewProps> {
case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />);
case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} />);
case CollectionViewType.Staff: return (<CollectionStaffView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
+ case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
+ case CollectionViewType.Carousel: { return (<CollectionCarouselView key="collview" {...props} />); }
case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
- case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (<CollectionFreeFormView key="collview" {...props} />); }
+ case CollectionViewType.Pivot: { return (<CollectionPivotView key="collview" {...props} />); }
case CollectionViewType.Freeform:
- default: { this.props.Document.freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
+ default: { this.props.Document._freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
}
}
@action
private collapse = (value: boolean) => {
this._collapsed = value;
- this.props.Document.chromeStatus = value ? "collapsed" : "enabled";
+ this.props.Document._chromeStatus = value ? "collapsed" : "enabled";
}
private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
// currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
- const chrome = this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking ? (null) :
+ const chrome = this.props.Document._chromeStatus === "disabled" || type === CollectionViewType.Docking ? (null) :
<CollectionViewBaseChrome CollectionView={this} key="chrome" type={type} collapse={this.collapse} />;
return [chrome, this.SubViewHelper(type, renderProps)];
}
@@ -199,23 +208,25 @@ export class CollectionView extends Touchable<FieldViewProps> {
if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
const existingVm = ContextMenu.Instance.findByDescription("View Modes...");
const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
- subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; }, icon: "signature" });
+ subItems.push({ description: "Freeform", event: () => { this.props.Document._viewType = CollectionViewType.Freeform; }, icon: "signature" });
if (CollectionView._safeMode) {
- ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" });
+ ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document._viewType = CollectionViewType.Invalid, icon: "project-diagram" });
}
- subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" });
- subItems.push({ description: "Treeview", event: () => this.props.Document.viewType = CollectionViewType.Tree, icon: "tree" });
- subItems.push({ description: "Stacking", event: () => this.props.Document.viewType = CollectionViewType.Stacking, icon: "ellipsis-v" });
+ subItems.push({ description: "Schema", event: () => this.props.Document._viewType = CollectionViewType.Schema, icon: "th-list" });
+ subItems.push({ description: "Treeview", event: () => this.props.Document._viewType = CollectionViewType.Tree, icon: "tree" });
+ subItems.push({ description: "Stacking", event: () => this.props.Document._viewType = CollectionViewType.Stacking, icon: "ellipsis-v" });
subItems.push({
description: "Stacking (AutoHeight)", event: () => {
- this.props.Document.viewType = CollectionViewType.Stacking;
- this.props.Document.autoHeight = true;
+ this.props.Document._viewType = CollectionViewType.Stacking;
+ this.props.Document._autoHeight = true;
}, icon: "ellipsis-v"
});
- subItems.push({ description: "Staff", event: () => this.props.Document.viewType = CollectionViewType.Staff, icon: "music" });
- subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" });
- subItems.push({ description: "Pivot", event: () => this.props.Document.viewType = CollectionViewType.Pivot, icon: "columns" });
- switch (this.props.Document.viewType) {
+ subItems.push({ description: "Staff", event: () => this.props.Document._viewType = CollectionViewType.Staff, icon: "music" });
+ subItems.push({ description: "Multicolumn", event: () => this.props.Document._viewType = CollectionViewType.Multicolumn, icon: "columns" });
+ subItems.push({ description: "Masonry", event: () => this.props.Document._viewType = CollectionViewType.Masonry, icon: "columns" });
+ subItems.push({ description: "Carousel", event: () => this.props.Document._viewType = CollectionViewType.Carousel, icon: "columns" });
+ subItems.push({ description: "Pivot", event: () => this.props.Document._viewType = CollectionViewType.Pivot, icon: "columns" });
+ switch (this.props.Document._viewType) {
case CollectionViewType.Freeform: {
subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
break;
@@ -227,12 +238,23 @@ export class CollectionView extends Touchable<FieldViewProps> {
const existing = ContextMenu.Instance.findByDescription("Layout...");
const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
+ if (this.props.Document.childLayout instanceof Doc) {
+ layoutItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, undefined, "onRight"), icon: "project-diagram" });
+ }
+ if (this.props.Document.childDetailed instanceof Doc) {
+ layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailed as Doc, undefined, "onRight"), icon: "project-diagram" });
+ }
!existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
const more = ContextMenu.Instance.findByDescription("More...");
const moreItems = more && "subitems" in more ? more.subitems : [];
moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
!more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+
+ const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+ const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+ onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
}
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 64411b5fe..414bbfc0b 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -9,8 +9,7 @@
background: lightgrey;
.collectionViewChrome {
- display: grid;
- grid-template-columns: 1fr auto;
+ display: flex;
padding-bottom: 10px;
border-bottom: .5px solid rgb(180, 180, 180);
overflow: hidden;
@@ -20,7 +19,7 @@
.collectionViewBaseChrome-viewPicker {
font-size: 75%;
- text-transform: uppercase;
+ //text-transform: uppercase;
letter-spacing: 2px;
background: rgb(238, 238, 238);
color: grey;
@@ -34,6 +33,26 @@
outline-color: black;
}
+ .collectionViewBaseChrome-cmdPicker {
+ margin-left: 3px;
+ margin-right: 0px;
+ background: rgb(238, 238, 238);
+ border: none;
+ color: grey;
+ }
+ .commandEntry-outerDiv {
+ pointer-events: all;
+ background-color: gray;
+ display: flex;
+ flex-direction: row;
+ .commandEntry-drop {
+ color:white;
+ width:25px;
+ margin-top: auto;
+ margin-bottom: auto;
+ }
+ }
+
.collectionViewBaseChrome-collapse {
transition: all .5s, opacity 0.3s;
position: absolute;
@@ -53,6 +72,18 @@
.collectionViewBaseChrome-viewSpecs {
margin-left: 10px;
display: grid;
+
+ .collectionViewBaseChrome-filterIcon {
+ position: relative;
+ display: flex;
+ margin: auto;
+ background: gray;
+ color: white;
+ width: 40px;
+ height: 40px;
+ align-items: center;
+ justify-content: center;
+ }
.collectionViewBaseChrome-viewSpecsInput {
padding: 12px 10px 11px 10px;
@@ -240,14 +271,13 @@
.commandEntry-outerDiv {
display: flex;
flex-direction: column;
- width: 165px;
height: 40px;
}
.commandEntry-inputArea {
display:flex;
flex-direction: row;
width: 150px;
- margin: auto 0 auto auto;
+ margin: auto auto auto auto;
}
.react-autosuggest__container {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 01dc21f95..0811654bf 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -39,31 +39,39 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
_templateCommand = {
- title: "set template", script: "setChildLayout(this.target, this.source && this.source.length ? this.source[0]:undefined)", params: ["target", "source"],
+ title: "=> item view", script: "setChildLayout(this.target, this.source?.[0])", params: ["target", "source"],
initialize: emptyFunction,
immediate: (draggedDocs: Doc[]) => Doc.setChildLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined)
};
+ _narrativeCommand = {
+ title: "=> click item view", script: "setChildDetailedLayout(this.target, this.source?.[0])", params: ["target", "source"],
+ initialize: emptyFunction,
+ immediate: (draggedDocs: Doc[]) => Doc.setChildDetailedLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined)
+ };
_contentCommand = {
- // title: "set content", script: "getProto(this.target).data = aliasDocs(this.source.map(async p => await p));", params: ["target", "source"], // bcz: doesn't look like we can do async stuff in scripting...
- title: "set content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"],
+ title: "=> content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"],
initialize: emptyFunction,
immediate: (draggedDocs: Doc[]) => Doc.GetProto(this.props.CollectionView.props.Document).data = new List<Doc>(draggedDocs.map((d: any) => Doc.MakeAlias(d)))
};
_viewCommand = {
- title: "restore view", script: "this.target.panX = this.restoredPanX; this.target.panY = this.restoredPanY; this.target.scale = this.restoredScale;", params: ["target"],
- immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document.panX = 0; this.props.CollectionView.props.Document.panY = 0; this.props.CollectionView.props.Document.scale = 1; },
- initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document.panX; button.restoredPanY = this.props.CollectionView.props.Document.panY; button.restoredScale = this.props.CollectionView.props.Document.scale; }
+ title: "=> saved view", script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;", params: ["target"],
+ initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document._panX; button.restoredPanY = this.props.CollectionView.props.Document._panY; button.restoredScale = this.props.CollectionView.props.Document.scale; },
+ immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document._panX = 0; this.props.CollectionView.props.Document._panY = 0; this.props.CollectionView.props.Document.scale = 1; },
};
- _freeform_commands = [this._contentCommand, this._templateCommand, this._viewCommand];
+ _freeform_commands = [this._contentCommand, this._templateCommand, this._narrativeCommand, this._viewCommand];
_stacking_commands = [this._contentCommand, this._templateCommand];
_masonry_commands = [this._contentCommand, this._templateCommand];
+ _schema_commands = [this._templateCommand, this._narrativeCommand];
_tree_commands = [];
private get _buttonizableCommands() {
switch (this.props.type) {
case CollectionViewType.Tree: return this._tree_commands;
+ case CollectionViewType.Schema: return this._schema_commands;
case CollectionViewType.Stacking: return this._stacking_commands;
case CollectionViewType.Masonry: return this._stacking_commands;
case CollectionViewType.Freeform: return this._freeform_commands;
+ case CollectionViewType.Pivot: return this._freeform_commands;
+ case CollectionViewType.Carousel: return this._freeform_commands;
}
return [];
}
@@ -126,7 +134,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
runInAction(() => {
this.addKeyRestrictions(fields);
// chrome status is one of disabled, collapsed, or visible. this determines initial state from document
- const chromeStatus = this.props.CollectionView.props.Document.chromeStatus;
+ const chromeStatus = this.props.CollectionView.props.Document._chromeStatus;
if (chromeStatus) {
if (chromeStatus === "disabled") {
throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
@@ -143,24 +151,35 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@undoBatch
viewChanged = (e: React.ChangeEvent) => {
//@ts-ignore
- this.props.CollectionView.props.Document.viewType = parseInt(e.target.selectedOptions[0].value);
+ this.props.CollectionView.props.Document._viewType = parseInt(e.target.selectedOptions[0].value);
+ }
+
+ commandChanged = (e: React.ChangeEvent) => {
+ //@ts-ignore
+ runInAction(() => this._currentKey = e.target.selectedOptions[0].value);
}
@action
openViewSpecs = (e: React.SyntheticEvent) => {
- this._viewSpecsOpen = true;
+ if (this._viewSpecsOpen) this.closeViewSpecs();
+ else {
+ this._viewSpecsOpen = true;
- //@ts-ignore
- if (!e.target.classList[0].startsWith("qs")) {
- this.closeDatePicker();
- }
+ //@ts-ignore
+ if (!e.target ?.classList[0] ?.startsWith("qs")) {
+ this.closeDatePicker();
+ }
- e.stopPropagation();
- document.removeEventListener("pointerdown", this.closeViewSpecs);
- document.addEventListener("pointerdown", this.closeViewSpecs);
+ e.stopPropagation();
+ document.removeEventListener("pointerdown", this.closeViewSpecs);
+ document.addEventListener("pointerdown", this.closeViewSpecs);
+ }
}
- @action closeViewSpecs = () => { this._viewSpecsOpen = false; document.removeEventListener("pointerdown", this.closeViewSpecs); };
+ @action closeViewSpecs = () => {
+ this._viewSpecsOpen = false;
+ document.removeEventListener("pointerdown", this.closeViewSpecs);
+ };
@action
openDatePicker = (e: React.PointerEvent) => {
@@ -231,10 +250,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@action
toggleCollapse = () => {
- console.log("toggle collapse");
- this.props.CollectionView.props.Document.chromeStatus = this.props.CollectionView.props.Document.chromeStatus === "enabled" ? "collapsed" : "enabled";
+ this.props.CollectionView.props.Document._chromeStatus = this.props.CollectionView.props.Document._chromeStatus === "enabled" ? "collapsed" : "enabled";
if (this.props.collapse) {
- this.props.collapse(this.props.CollectionView.props.Document.chromeStatus !== "enabled");
+ this.props.collapse(this.props.CollectionView.props.Document._chromeStatus !== "enabled");
}
}
@@ -262,7 +280,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@observable private pivotKeyDisplay = this.pivotKey;
getPivotInput = () => {
- if (StrCast(this.document.freeformLayoutEngine) !== "pivot") {
+ if (StrCast(this.document._freeformLayoutEngine) !== "pivot") {
return (null);
}
return (<input className="collectionViewBaseChrome-viewSpecsInput"
@@ -297,7 +315,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
if (de.complete.docDragData && de.complete.docDragData.draggedDocuments.length) {
- this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData?.draggedDocuments || []));
+ this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData ?.draggedDocuments || []));
e.stopPropagation();
}
return true;
@@ -373,7 +391,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
render() {
- const collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled";
+ const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
return (
<div className="collectionViewChrome-cont" style={{ top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined }}>
<div className="collectionViewChrome">
@@ -392,23 +410,21 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
className="collectionViewBaseChrome-viewPicker"
onPointerDown={stopPropagation}
onChange={this.viewChanged}
- value={NumCast(this.props.CollectionView.props.Document.viewType)}>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="7">Pivot View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="8">Linear View</option>
+ value={NumCast(this.props.CollectionView.props.Document._viewType)}>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">schema</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="7">Multicolumn</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="8">Pivot</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="9">Carousel</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="10">Linear</option>
</select>
- <div className="collectionViewBaseChrome-viewSpecs" style={{ display: collapsed ? "none" : "grid" }}>
- <input className="collectionViewBaseChrome-viewSpecsInput"
- placeholder="FILTER"
- value={this.filterValue ? this.filterValue.script.originalScript === "return true" ? "" : this.filterValue.script.originalScript : ""}
- onChange={(e) => { }}
- onPointerDown={this.openViewSpecs}
- id="viewSpecsInput" />
- {this.getPivotInput()}
+ <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
+ <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.openViewSpecs} >
+ <FontAwesomeIcon icon="filter" size="2x" />
+ </div>
<div className="collectionViewBaseChrome-viewSpecsMenu"
onPointerDown={this.openViewSpecs}
style={{
@@ -449,17 +465,20 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
</div>
</div>
<div className="collectionViewBaseChrome-template" ref={this.createDropTarget} >
- <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
- <div className="commandEntry-inputArea" onPointerDown={this.autoSuggestDown} >
- <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
- getSuggestionValue={this.getSuggestionValue}
- suggestions={this.suggestions}
- alwaysRenderSuggestions={true}
- renderSuggestion={this.renderSuggestion}
- onSuggestionsFetchRequested={this.onSuggestionFetch}
- onSuggestionsClearRequested={this.onSuggestionClear}
- ref={this._autosuggestRef} />
+ <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
+ <div className="commandEntry-drop">
+ <FontAwesomeIcon icon="bullseye" size="2x"></FontAwesomeIcon>
</div>
+ <select
+ className="collectionViewBaseChrome-cmdPicker"
+ onPointerDown={stopPropagation}
+ onChange={this.commandChanged}
+ value={this._currentKey}>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""}>{""}</option>
+ {this._buttonizableCommands.map(cmd =>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
+ )}
+ </select>
</div>
</div>
</div>
@@ -600,15 +619,6 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewCh
return (
<div className="collectionSchemaViewChrome-cont">
<div className="collectionSchemaViewChrome-toggle">
- <div className="collectionSchemaViewChrome-label">Wrap Text: </div>
- <div className="collectionSchemaViewChrome-toggler" onClick={this.toggleTextwrap}>
- <div className={"collectionSchemaViewChrome-togglerButton" + (textWrapped ? " on" : " off")}>
- {textWrapped ? "on" : "off"}
- </div>
- </div>
- </div>
-
- <div className="collectionSchemaViewChrome-toggle">
<div className="collectionSchemaViewChrome-label">Show Preview: </div>
<div className="collectionSchemaViewChrome-toggler" onClick={this.togglePreview}>
<div className={"collectionSchemaViewChrome-togglerButton" + (previewWidth !== 0 ? " on" : " off")}>
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
index d293bb5ca..a266861bd 100644
--- a/src/client/views/collections/ParentDocumentSelector.scss
+++ b/src/client/views/collections/ParentDocumentSelector.scss
@@ -35,8 +35,6 @@
pointer-events: all;
position: relative;
display: inline-block;
- padding-left: 5px;
- padding-right: 5px;
}
.parentDocumentSelector-metadata {
pointer-events: auto;
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 24aa6ddfa..115f8d633 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -6,7 +6,7 @@ import { observable, action, runInAction } from "mobx";
import { Id } from "../../../new_fields/FieldSymbols";
import { SearchUtil } from "../../util/SearchUtil";
import { CollectionDockingView } from "./CollectionDockingView";
-import { NumCast } from "../../../new_fields/Types";
+import { NumCast, StrCast } from "../../../new_fields/Types";
import { CollectionViewType } from "./CollectionView";
import { DocumentButtonBar } from "../DocumentButtonBar";
import { DocumentManager } from "../../util/DocumentManager";
@@ -21,7 +21,13 @@ export const Flyout = higflyout.default;
library.add(faEdit);
-type SelectorProps = { Document: Doc, Views: DocumentView[], Stack?: any, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void };
+type SelectorProps = {
+ Document: Doc,
+ Views: DocumentView[],
+ Stack?: any,
+ addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void
+};
+
@observer
export class SelectorContextMenu extends React.Component<SelectorProps> {
@observable private _docs: { col: Doc, target: Doc }[] = [];
@@ -49,31 +55,22 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
getOnClick({ col, target }: { col: Doc, target: Doc }) {
return () => {
col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
- if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
- const newPanX = NumCast(target.x) + NumCast(target.width) / 2;
- const newPanY = NumCast(target.y) + NumCast(target.height) / 2;
- col.panX = newPanX;
- col.panY = newPanY;
+ if (NumCast(col._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(target.x) + NumCast(target._width) / 2;
+ const newPanY = NumCast(target.y) + NumCast(target._height) / 2;
+ col._panX = newPanX;
+ col._panY = newPanY;
}
this.props.addDocTab(col, undefined, "inTab"); // bcz: dataDoc?
};
}
- get metadataMenu() {
- return <div className="parentDocumentSelector-metadata">
- <Flyout anchorPoint={anchorPoints.TOP_LEFT}
- content={<MetadataEntryMenu docs={() => this.props.Views.map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */}
- <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div>
- </Flyout>
- </div>;
- }
render() {
return <div >
- <div key="metadata">Metadata: {this.metadataMenu}</div>
<p key="contexts">Contexts:</p>
- {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
+ {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title?.toString()}</a></p>)}
{this._otherDocs.length ? <hr key="hr" /> : null}
- {this._otherDocs.map(doc => <p key="p"><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
+ {this._otherDocs.map(doc => <p key={"p" + doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title?.toString()}</a></p>)}
</div>;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 012115b1f..be1317b25 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -7,8 +7,9 @@ import { OverlayView, OverlayElementOptions } from "../../OverlayView";
import { emptyFunction } from "../../../../Utils";
import React = require("react");
import { ObservableMap, runInAction } from "mobx";
-import { Id } from "../../../../new_fields/FieldSymbols";
-import { DateField } from "../../../../new_fields/DateField";
+import { Id, ToString } from "../../../../new_fields/FieldSymbols";
+import { ObjectField } from "../../../../new_fields/ObjectField";
+import { RefField } from "../../../../new_fields/RefField";
interface PivotData {
type: string;
@@ -35,21 +36,19 @@ export interface ViewDefResult {
}
function toLabel(target: FieldResult<Field>) {
- if (target instanceof DateField) {
- const date = DateCast(target).date;
- if (date) {
- return `${date.toDateString()} ${date.toTimeString()}`;
- }
+ if (target instanceof ObjectField || target instanceof RefField) {
+ return target[ToString]();
}
return String(target);
}
-export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
+export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200);
const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
+ const pivotFieldKey = toLabel(pivotDoc.pivotField);
for (const doc of childDocs) {
- const val = doc[StrCast(pivotDoc.pivotField, "title")];
+ const val = Field.toString(doc[pivotFieldKey] as Field);
if (val) {
!pivotColumnGroups.get(val) && pivotColumnGroups.set(val, []);
pivotColumnGroups.get(val)!.push(doc);
@@ -57,9 +56,10 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
}
const minSize = Array.from(pivotColumnGroups.entries()).reduce((min, pair) => Math.min(min, pair[1].length), Infinity);
- const numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize)));
+ let numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize)));
const docMap = new Map<Doc, ViewDefBounds>();
const groupNames: PivotData[] = [];
+ numCols = Math.min(panelDim[0] / pivotAxisWidth, numCols);
const expander = 1.05;
const gap = .15;
@@ -73,26 +73,26 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
x,
y: pivotAxisWidth + 50,
width: pivotAxisWidth * expander * numCols,
- height: 100,
+ height: NumCast(pivotDoc.pivotFontSize, 10),
fontSize: NumCast(pivotDoc.pivotFontSize, 10)
});
for (const doc of val) {
const layoutDoc = Doc.Layout(doc);
let wid = pivotAxisWidth;
- let hgt = layoutDoc.nativeWidth ? (NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
+ let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
if (hgt > pivotAxisWidth) {
hgt = pivotAxisWidth;
- wid = layoutDoc.nativeHeight ? (NumCast(layoutDoc.nativeWidth) / NumCast(layoutDoc.nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
+ wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
}
docMap.set(doc, {
- x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2,
+ x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.length < numCols ? (numCols - val.length) * pivotAxisWidth / 2 : 0),
y: -y,
width: wid,
height: hgt
});
xCount++;
if (xCount >= numCols) {
- xCount = (pivotAxisWidth - wid) / 2;
+ xCount = 0;
y += pivotAxisWidth * expander;
}
}
@@ -104,8 +104,8 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
x: NumCast(pair.layout.x),
y: NumCast(pair.layout.y),
z: NumCast(pair.layout.z),
- width: NumCast(pair.layout.width),
- height: NumCast(pair.layout.height)
+ width: NumCast(pair.layout._width),
+ height: NumCast(pair.layout._height)
};
const pos = docMap.get(pair.layout) || defaultPosition;
const data = poolData.get(pair.layout[Id]);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 178a5bcdc..b8fbaef5c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -54,8 +54,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
} else {
setTimeout(() => {
(this.props.A.props.Document[(this.props.A.props as any).fieldKey] as Doc);
- let m = targetBhyperlink.getBoundingClientRect();
- let mp = this.props.A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+ const m = targetBhyperlink.getBoundingClientRect();
+ const mp = this.props.A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
this.props.A.props.Document[afield + "_x"] = mp[0] / this.props.A.props.PanelWidth() * 100;
this.props.A.props.Document[afield + "_y"] = mp[1] / this.props.A.props.PanelHeight() * 100;
}, 0);
@@ -66,8 +66,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
} else {
setTimeout(() => {
(this.props.B.props.Document[(this.props.B.props as any).fieldKey] as Doc);
- let m = targetAhyperlink.getBoundingClientRect();
- let mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+ const m = targetAhyperlink.getBoundingClientRect();
+ const mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
this.props.B.props.Document[afield + "_x"] = mp[0] / this.props.B.props.PanelWidth() * 100;
this.props.B.props.Document[afield + "_y"] = mp[1] / this.props.B.props.PanelHeight() * 100;
}, 0);
@@ -93,8 +93,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
apt.point.x, apt.point.y);
const pt1 = [apt.point.x, apt.point.y];
const pt2 = [bpt.point.x, bpt.point.y];
- let aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
- let bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
+ const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
+ const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
return !aActive && !bActive ? (null) :
<line key="linkLine" className="collectionfreeformlinkview-linkLine"
style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4d216cb2a..d988ff241 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -3,13 +3,13 @@ import { faEye } from "@fortawesome/free-regular-svg-icons";
import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc";
import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
import { Id } from "../../../../new_fields/FieldSymbols";
import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, DateCast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types";
+import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast, FieldValue } from "../../../../new_fields/Types";
import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
@@ -27,7 +27,7 @@ import { ContextMenu } from "../../ContextMenu";
import { ContextMenuProps } from "../../ContextMenuItem";
import { InkingControl } from "../../InkingControl";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentViewProps } from "../../nodes/DocumentView";
+import { DocumentContentsView } from "../../nodes/DocumentContentsView";
import { FormattedTextBox } from "../../nodes/FormattedTextBox";
import { pageSchema } from "../../nodes/ImageBox";
import PDFMenu from "../../pdf/PDFMenu";
@@ -41,21 +41,23 @@ import React = require("react");
import { computedFn } from "mobx-utils";
import { TraceMobx } from "../../../../new_fields/util";
import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { LinkManager } from "../../../util/LinkManager";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import Palette from "../../Palette";
+import { RichTextField } from "../../../../new_fields/RichTextField";
+import { List } from "../../../../new_fields/List";
+import { DocumentViewProps } from "../../nodes/DocumentView";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
export const panZoomSchema = createSchema({
- panX: "number",
- panY: "number",
+ _panX: "number",
+ _panY: "number",
scale: "number",
arrangeScript: ScriptField,
arrangeInit: ScriptField,
useClusters: "boolean",
- isRuleProvider: "boolean",
fitToBox: "boolean",
+ xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
+ yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
panTransformType: "string",
scrollHeight: "number",
fitX: "number",
@@ -71,25 +73,28 @@ const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSch
export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private _lastX: number = 0;
private _lastY: number = 0;
+ private _inkToTextStartX: number | undefined;
+ private _inkToTextStartY: number | undefined;
+ private _wordPalette: Map<string, string> = new Map<string, string>();
private _clusterDistance: number = 75;
private _hitCluster = false;
private _layoutComputeReaction: IReactionDisposer | undefined;
private _layoutPoolData = new ObservableMap<string, any>();
- public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
+ public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title ?.toString() + ")"; } // this makes mobx trace() statements more descriptive
@observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@observable _clusterSets: (Doc[])[] = [];
- @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
+ @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
@computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
- @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); }
- @computed get nativeWidth() { return this.Document.fitToContent ? 0 : this.Document.nativeWidth || 0; }
- @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; }
+ @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); }
+ @computed get nativeWidth() { return this.Document._fitToContent ? 0 : NumCast(this.Document._nativeWidth); }
+ @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight); }
private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
private easing = () => this.props.Document.panTransformType === "Ease";
- private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document.panX || 0;
- private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document.panY || 0;
+ private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
+ private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document._panY || 0;
private zoomScaling = () => (1 / this.parentScaling) * (this.fitToContent ?
Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
this.Document.scale || 1)
@@ -101,14 +106,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
private addLiveTextBox = (newBox: Doc) => {
FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
- const maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
- let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0;
- if (heading === 0) {
- const sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
- DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
- heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
- }
- !this.Document.isRuleProvider && (newBox.heading = heading);
this.addDocument(newBox);
}
private addDocument = (newBox: Doc) => {
@@ -153,18 +150,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const layoutDoc = Doc.Layout(d);
d.x = x + NumCast(d.x) - dropX;
d.y = y + NumCast(d.y) - dropY;
- if (!NumCast(layoutDoc.width)) {
- layoutDoc.width = 300;
+ if (!NumCast(layoutDoc._width)) {
+ layoutDoc._width = 300;
}
- if (!NumCast(layoutDoc.height)) {
- const nw = NumCast(layoutDoc.nativeWidth);
- const nh = NumCast(layoutDoc.nativeHeight);
- layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300;
+ if (!NumCast(layoutDoc._height)) {
+ const nw = NumCast(layoutDoc._nativeWidth);
+ const nh = NumCast(layoutDoc._nativeHeight);
+ layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
}
this.bringToFront(d);
}));
- de.complete.docDragData.droppedDocuments.length === 1 && this.updateCluster(de.complete.docDragData.droppedDocuments[0]);
+ (de.complete.docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(de.complete.docDragData.droppedDocuments);
}
}
else if (de.complete.annoDragData) {
@@ -189,8 +186,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
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 cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance;
+ const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance;
return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ?
NumCast(cd.cluster) : cluster;
}, -1);
@@ -222,6 +219,41 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
}
+ @action
+ updateClusterDocs(docs: Doc[]) {
+ const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
+ if (this.props.Document.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);
+ 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.cluster === -1 && preferredInd !== -1 && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
+ docFirst.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.cluster === -1) {
+ docs.map(doc => {
+ doc.cluster = this._clusterSets.length;
+ this._clusterSets.push([doc]);
+ });
+ } else {
+ 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));
+ }
+ childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.cluster === i) && this.updateCluster(child));
+ childLayouts.map(child => Doc.GetProto(child).clusterStr = child.cluster ?.toString());
+ }
+ }
+
@undoBatch
@action
updateCluster(doc: Doc) {
@@ -275,11 +307,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
return;
}
this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
- if (e.button === 0 && !e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+ if (e.button === 0 && (!e.shiftKey || this._hitCluster) && !e.altKey && !e.ctrlKey && this.props.active(true)) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
@@ -324,30 +356,32 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
@action
- handle1PointerDown = (e: React.TouchEvent) => {
- const pt = e.targetTouches.item(0);
- if (pt) {
- this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false;
- if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
- document.removeEventListener("touchmove", this.onTouch);
- document.addEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
- document.addEventListener("touchend", this.onTouchEnd);
- // if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) {
- // e.stopPropagation();
- // e.preventDefault();
- // const point = this.getTransform().transformPoint(pt.pageX, pt.pageY);
- // this._points.push({ X: point[0], Y: point[1] });
- // }
- if (InkingControl.Instance.selectedTool === InkTool.None) {
- this._lastX = pt.pageX;
- this._lastY = pt.pageY;
- e.stopPropagation();
- e.preventDefault();
- }
- else {
- e.stopPropagation();
- e.preventDefault();
+ handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
+ if (!e.nativeEvent.cancelBubble) {
+ // const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
+ const pt = me.changedTouches[0];
+ if (pt) {
+ this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false;
+ if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ // if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) {
+ // e.stopPropagation();
+ // e.preventDefault();
+ // const point = this.getTransform().transformPoint(pt.pageX, pt.pageY);
+ // this._points.push({ X: point[0], Y: point[1] });
+ // }
+ if (InkingControl.Instance.selectedTool === InkTool.None) {
+ this._lastX = pt.pageX;
+ this._lastY = pt.pageY;
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ else {
+ e.preventDefault();
+ }
}
}
}
@@ -359,7 +393,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
case GestureUtils.Gestures.Stroke:
const points = ge.points;
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
- const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height });
+ const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
this.addDocument(inkDoc);
e.stopPropagation();
break;
@@ -381,10 +415,80 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
return pass;
});
- this.addDocument(Docs.Create.FreeformDocument(sel, { x: bounds.x, y: bounds.y, width: bWidth, height: bHeight, panX: 0, panY: 0 }));
+ this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
sel.forEach(d => this.props.removeDocument(d));
+ e.stopPropagation();
break;
-
+ case GestureUtils.Gestures.StartBracket:
+ const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
+ this._inkToTextStartX = start[0];
+ this._inkToTextStartY = start[1];
+ console.log("start");
+ break;
+ case GestureUtils.Gestures.EndBracket:
+ console.log("end");
+ if (this._inkToTextStartX && this._inkToTextStartY) {
+ const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
+ const setDocs = this.getActiveDocuments().filter(s => s.proto ?.type === "text" && s.color);
+ const sets = setDocs.map((sd) => {
+ return Cast(sd.data, RichTextField) ?.Text as string;
+ });
+ if (sets.length && sets[0]) {
+ this._wordPalette.clear();
+ const colors = setDocs.map(sd => FieldValue(sd.color) as string);
+ sets.forEach((st: string, i: number) => {
+ const words = st.split(",");
+ words.forEach(word => {
+ this._wordPalette.set(word, colors[i]);
+ });
+ });
+ }
+ const inks = this.getActiveDocuments().filter(doc => {
+ if (doc.type === "ink") {
+ const l = NumCast(doc.x);
+ const r = l + doc[WidthSym]();
+ const t = NumCast(doc.y);
+ const b = t + doc[HeightSym]();
+ const pass = !(this._inkToTextStartX! > r || end[0] < l || this._inkToTextStartY! > b || end[1] < t);
+ return pass;
+ }
+ return false;
+ });
+ const inkFields = inks.map(i => Cast(i.data, InkField));
+ CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
+ const wordResults = results.filter((r: any) => r.category === "inkWord");
+ for (const word of wordResults) {
+ const indices: number[] = word.strokeIds;
+ indices.forEach(i => {
+ const otherInks: Doc[] = [];
+ indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2]));
+ inks[i].relatedInks = new List<Doc>(otherInks);
+ const uniqueColors: string[] = [];
+ Array.from(this._wordPalette.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c));
+ inks[i].alternativeColors = new List<string>(uniqueColors);
+ if (this._wordPalette.has(word.recognizedText.toLowerCase())) {
+ inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase());
+ }
+ else {
+ for (const alt of word.alternates) {
+ if (this._wordPalette.has(alt.recognizedString.toLowerCase())) {
+ inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase());
+ break;
+ }
+ }
+ }
+ });
+ }
+ });
+ this._inkToTextStartX = end[0];
+ }
+ break;
+ case GestureUtils.Gestures.Text:
+ if (ge.text) {
+ const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y);
+ this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
+ e.stopPropagation();
+ }
}
}
@@ -394,8 +498,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- document.removeEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
+ this.removeMoveListeners();
+ this.removeEndListeners();
}
@action
@@ -403,22 +507,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// I think it makes sense for the marquee menu to go away when panned. -syip2
MarqueeOptionsMenu.Instance && MarqueeOptionsMenu.Instance.fadeOut(true);
- let x = this.Document.panX || 0;
- let y = this.Document.panY || 0;
- const docs = this.childLayoutPairs.map(pair => pair.layout);
+ let x = this.Document._panX || 0;
+ let y = this.Document._panY || 0;
+ const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout);
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- if (!this.isAnnotationOverlay) {
- PDFMenu.Instance && PDFMenu.Instance.fadeOut(true);
- const minx = docs.length ? NumCast(docs[0].x) : 0;
- const maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
- const miny = docs.length ? NumCast(docs[0].y) : 0;
- const maxy = docs.length ? NumCast(docs[0].height) + miny : miny;
+ if (!this.isAnnotationOverlay && docs.length) {
+ PDFMenu.Instance.fadeOut(true);
+ const minx = this.childDataProvider(docs[0]).x;//docs.length ? NumCast(docs[0].x) : 0;
+ const miny = this.childDataProvider(docs[0]).y;//docs.length ? NumCast(docs[0].y) : 0;
+ const maxx = this.childDataProvider(docs[0]).width + minx;//docs.length ? NumCast(docs[0].width) + minx : minx;
+ const maxy = this.childDataProvider(docs[0]).height + miny;//docs.length ? NumCast(docs[0].height) + miny : miny;
const ranges = docs.filter(doc => doc).reduce((range, doc) => {
- const layoutDoc = Doc.Layout(doc);
- const x = NumCast(doc.x);
- const xe = x + NumCast(layoutDoc.width);
- const y = NumCast(doc.y);
- const ye = y + NumCast(layoutDoc.height);
+ const x = this.childDataProvider(doc).x;//NumCast(doc.x);
+ const y = this.childDataProvider(doc).y;//NumCast(doc.y);
+ const xe = this.childDataProvider(doc).width + x;//x + NumCast(layoutDoc.width);
+ const ye = this.childDataProvider(doc).height + y; //y + NumCast(layoutDoc.height);
return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
[range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
}, [[minx, maxx], [miny, maxy]]);
@@ -464,10 +567,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
- handle1PointerMove = (e: TouchEvent) => {
+ handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
// panning a workspace
if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
+ const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
const pt = myTouches[0];
if (pt) {
if (InkingControl.Instance.selectedTool === InkTool.None) {
@@ -481,17 +584,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.pan(pt);
}
}
- e.stopPropagation();
+ // e.stopPropagation();
e.preventDefault();
}
}
- handle2PointersMove = (e: TouchEvent) => {
+ handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
// pinch zooming
if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
+ const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
const pt1 = myTouches[0];
const pt2 = myTouches[1];
+ console.log(myTouches);
if (this.prevPoints.size === 2) {
const oldPoint1 = this.prevPoints.get(pt1.identifier);
@@ -526,35 +630,39 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
}
- e.stopPropagation();
+ // e.stopPropagation();
e.preventDefault();
}
}
@action
- handle2PointersDown = (e: React.TouchEvent) => {
+ handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
if (!e.nativeEvent.cancelBubble && this.props.active(true)) {
- const pt1: React.Touch | null = e.targetTouches.item(0);
- const pt2: React.Touch | null = e.targetTouches.item(1);
- if (!pt1 || !pt2) return;
-
- 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;
- document.removeEventListener("touchmove", this.onTouch);
- document.addEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
- document.addEventListener("touchend", this.onTouchEnd);
- e.stopPropagation();
+ // 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 = () => {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- document.removeEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
+ this.removeMoveListeners();
+ this.removeEndListeners();
}
@action
@@ -593,8 +701,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
- this.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
- this.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
+ this.Document._panX = this.isAnnotationOverlay ? newPanX : panX;
+ this.Document._panY = this.isAnnotationOverlay ? newPanY : panY;
}
}
@@ -618,14 +726,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// 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) {
+ 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: this.Document.panX, panY: this.Document.panY };
+ state.initializers![this.Document[Id]] = { panX: this.Document._panX, panY: this.Document._panY };
HistoryUtil.pushState(state);
- } else if (init.panX !== this.Document.panX || init.panY !== this.Document.panY) {
- init.panX = this.Document.panX;
- init.panY = this.Document.panY;
+ } else if (init.panX !== this.Document._panX || init.panY !== this.Document._panY) {
+ init.panX = this.Document._panX;
+ init.panY = this.Document._panY;
HistoryUtil.pushState(state);
}
}
@@ -641,13 +749,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
} else {
const layoutdoc = Doc.Layout(doc);
- const newPanX = NumCast(doc.x) + NumCast(layoutdoc.width) / 2;
- const newPanY = NumCast(doc.y) + NumCast(layoutdoc.height) / 2;
+ const newPanX = NumCast(doc.x) + NumCast(layoutdoc._width) / 2;
+ const newPanY = NumCast(doc.y) + NumCast(layoutdoc._height) / 2;
const newState = HistoryUtil.getState();
newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
HistoryUtil.pushState(newState);
- const savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType };
+ const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // 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
Doc.BrushDoc(this.props.Document);
@@ -657,8 +765,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
afterFocus && setTimeout(() => {
if (afterFocus && afterFocus()) {
- this.Document.panX = savedState.px;
- this.Document.panY = savedState.py;
+ this.Document._panX = savedState.px;
+ this.Document._panY = savedState.py;
this.Document.scale = savedState.s;
this.Document.panTransformType = savedState.pt;
}
@@ -668,7 +776,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
setScaleToZoom = (doc: Doc, scale: number = 0.5) => {
- this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc.width), this.props.PanelHeight() / NumCast(doc.height));
+ this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
}
zoomToScale = (scale: number) => {
@@ -678,6 +786,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
getScale = () => this.Document.scale || 1;
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
+ @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
@@ -686,8 +795,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
Document: childLayout,
LibraryPath: this.libraryPath,
layoutKey: undefined,
- ruleProvider: this.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, //bcz: hack! - currently ruleProviders apply to documents in nested colleciton, not direct children of themselves
- onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
+ //onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
+ onClick: this.onChildClickHandler,
ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
renderDepth: this.props.renderDepth + 1,
PanelWidth: childLayout[WidthSym],
@@ -705,12 +814,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } {
- const result = this.Document.arrangeScript?.script.run(params, console.log);
- if (result?.success) {
+ const result = this.Document.arrangeScript ?.script.run(params, console.log);
+ if (result ?.success) {
return { ...result, transition: "transform 1s" };
}
const layoutDoc = Doc.Layout(params.doc);
- return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(layoutDoc.width, "number"), height: Cast(layoutDoc.height, "number") };
+ return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number") };
}
viewDefsToJSX = (views: any[]) => {
@@ -728,7 +837,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const fontSize = Cast(viewDef.fontSize, "number");
return [text, x, y, width, height].some(val => val === undefined) ? undefined :
{
- ele: <div className="collectionFreeform-customText" style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}>
+ ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z} style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}>
{text}
</div>,
bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
@@ -736,11 +845,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
- childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) { return this._layoutPoolData.get(doc[Id]); }.bind(this));
+ childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) {
+ if (!doc) {
+ console.log(doc);
+ }
+ return this._layoutPoolData.get(doc[Id]);
+ }.bind(this));
doPivotLayout(poolData: ObservableMap<string, any>) {
return computePivotLayout(poolData, this.props.Document, this.childDocs,
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), this.viewDefsToJSX);
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
}
doFreeformLayout(poolData: ObservableMap<string, any>) {
@@ -762,15 +876,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
get doLayoutComputation() {
let computedElementData: { elements: ViewDefResult[] };
- switch (this.Document.freeformLayoutEngine) {
+ switch (this.Document._freeformLayoutEngine) {
case "pivot": computedElementData = this.doPivotLayout(this._layoutPoolData); break;
default: computedElementData = this.doFreeformLayout(this._layoutPoolData); break;
}
this.childLayoutPairs.filter((pair, i) => this.isCurrent(pair.layout)).forEach(pair =>
computedElementData.elements.push({
- ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.childDataProvider}
- ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
- jitterRotation={NumCast(this.props.Document.jitterRotation)} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
+ ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} {...this.getChildDocumentViewProps(pair.layout, pair.data)}
+ dataProvider={this.childDataProvider}
+ jitterRotation={NumCast(this.props.Document.jitterRotation)}
+ fitToBox={this.props.fitToBox || this.Document._freeformLayoutEngine === "pivot"} />,
bounds: this.childDataProvider(pair.layout)
}));
@@ -797,12 +912,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
layoutDocsInGrid = () => {
UndoManager.RunInBatch(() => {
const docs = DocListCast(this.Document[this.props.fieldKey]);
- const startX = this.Document.panX || 0;
+ const startX = this.Document._panX || 0;
let x = startX;
- let y = this.Document.panY || 0;
+ let y = this.Document._panY || 0;
let i = 0;
- const width = Math.max(...docs.map(doc => NumCast(doc.width)));
- const height = Math.max(...docs.map(doc => NumCast(doc.height)));
+ const width = Math.max(...docs.map(doc => NumCast(doc._width)));
+ const height = Math.max(...docs.map(doc => NumCast(doc._height)));
for (const doc of docs) {
doc.x = x;
doc.y = y;
@@ -816,37 +931,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}, "arrange contents");
}
- autoFormat = () => {
- this.Document.isRuleProvider = !this.Document.isRuleProvider;
- // find rule colorations when rule providing is turned on by looking at each document to see if it has a coloring -- if so, use it's color as the rule for its associated heading.
- this.Document.isRuleProvider && this.childLayoutPairs.map(pair =>
- // iterate over the children of a displayed document (or if the displayed document is a template, iterate over the children of that template)
- DocListCast(Doc.Layout(pair.layout).data).map(heading => {
- const headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading);
- const headingLayout = headingPair.layout && (pair.layout.data_ext instanceof Doc) && (pair.layout.data_ext[`Layout[${headingPair.layout[Id]}]`] as Doc) || headingPair.layout;
- if (headingLayout && NumCast(headingLayout.heading) > 0 && headingLayout.backgroundColor !== headingLayout.defaultBackgroundColor) {
- Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(headingLayout.heading)] = headingLayout.backgroundColor;
- }
- })
- );
- }
-
- analyzeStrokes = async () => {
- const children = await DocListCastAsync(this.dataDoc.data);
- if (!children) {
- return;
- }
- const inkData: InkData[] = [];
- for (const doc of children) {
- const data = Cast(doc.data, InkField)?.inkData;
- data && inkData.push(data);
- }
- if (!inkData.length) {
- return;
- }
- CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], inkData);
- }
-
private thumbIdentifier?: number;
// @action
@@ -893,16 +977,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
onContextMenu = (e: React.MouseEvent) => {
const layoutItems: ContextMenuProps[] = [];
- if (this.childDocs.some(d => BoolCast(d.isTemplateDoc))) {
- layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
- }
- layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
- layoutItems.push({ description: `${this.Document.LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document.LODdisable = !this.Document.LODdisable, icon: "table" });
- layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+ layoutItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+ layoutItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+ layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
- layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" });
layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
- layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
+ // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
layoutItems.push({
description: "Import document", icon: "upload", event: ({ x, y }) => {
@@ -936,7 +1016,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
description: "Add Note ...",
subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({
description: (i + 1) + ": " + StrCast(note.title),
- event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument({ width: 200, height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })),
+ event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument("", { _width: 200, _height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], _autoHeight: true, layout: note, title: StrCast(note.title) })),
icon: "eye"
})) as ContextMenuProps[],
icon: "eye"
@@ -957,7 +1037,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
children = () => {
const eles: JSX.Element[] = [];
- this.extensionDoc && (eles.push(...this.childViews()));
+ eles.push(...this.childViews());
// this._palette && (eles.push(this._palette));
// this.currentStroke && (eles.push(this.currentStroke));
eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
@@ -965,11 +1045,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
@computed get placeholder() {
return <div className="collectionfreeformview-placeholder" style={{ background: this.Document.backgroundColor }}>
- <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title}</span>
+ <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title ?.toString()}</span>
</div>;
}
@computed get marqueeView() {
- return <MarqueeView {...this.props} extensionDoc={this.extensionDoc!} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
+ return <MarqueeView {...this.props} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
<CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
@@ -977,6 +1057,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
</CollectionFreeFormViewPannableContents>
</MarqueeView>;
}
+
+ @computed get contentScaling() {
+ if (this.props.annotationsKey) return 0;
+ const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
+ const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1;
+ return wscale < hscale ? wscale : hscale;
+ }
render() {
TraceMobx();
// update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
@@ -986,12 +1073,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
// if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey.
// otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
- if (!this.extensionDoc) return (null);
// let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
- return <div className={"collectionfreeformview-container"} ref={this.createDropAndGestureTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
- style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
- {!this.Document.LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
+ return <div className={"collectionfreeformview-container"}
+ ref={this.createDashEventsTarget}
+ onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}
+ style={{
+ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
+ transformOrigin: this.contentScaling ? "left top" : "",
+ width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
+ height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+ }}>
+ {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
this.placeholder : this.marqueeView}
<CollectionFreeFormOverlayView elements={this.elementFunc} />
</div>;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 32e39d25e..db4b674b5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -11,6 +11,7 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+ public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public showMarquee: () => void = unimplementedFunction;
public hideMarquee: () => void = unimplementedFunction;
@@ -25,21 +26,31 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
<button
className="antimodeMenu-button"
title="Create a Collection"
+ key="group"
onPointerDown={this.createCollection}>
<FontAwesomeIcon icon="object-group" size="lg" />
</button>,
<button
className="antimodeMenu-button"
title="Summarize Documents"
+ key="summarize"
onPointerDown={this.summarize}>
<FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
</button>,
<button
className="antimodeMenu-button"
title="Delete Documents"
+ key="delete"
onPointerDown={this.delete}>
<FontAwesomeIcon icon="trash-alt" size="lg" />
</button>,
+ <button
+ className="antimodeMenu-button"
+ title="Change to Text"
+ key="inkToText"
+ onPointerDown={this.inkToText}>
+ <FontAwesomeIcon icon="font" size="lg" />
+ </button>,
];
return this.getElement(buttons);
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 523edb918..8591144c0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,12 +1,12 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, DataSym, WidthSym, HeightSym } from "../../../../new_fields/Doc";
import { InkField } from "../../../../new_fields/InkField";
import { List } from "../../../../new_fields/List";
import { listSpec } from "../../../../new_fields/Schema";
import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
import { ComputedField } from "../../../../new_fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { Cast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types";
import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
import { Utils } from "../../../../Utils";
import { Docs } from "../../../documents/Documents";
@@ -19,6 +19,8 @@ import "./MarqueeView.scss";
import React = require("react");
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import { SubCollectionViewProps } from "../CollectionSubView";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { RichTextField } from "../../../../new_fields/RichTextField";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -29,7 +31,6 @@ interface MarqueeViewProps {
removeDocument: (doc: Doc) => boolean;
addLiveTextDocument: (doc: Doc) => void;
isSelected: () => boolean;
- extensionDoc: Doc;
isAnnotationOverlay?: boolean;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
}
@@ -85,7 +86,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
ns.map(line => {
const indent = line.search(/\S|$/);
- const newBox = Docs.Create.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
+ const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + indent / 3 * 10, y: y, title: line });
this.props.addDocument(newBox);
y += 40 * this.props.getTransform().Scale;
});
@@ -95,17 +96,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
navigator.clipboard.readText().then(text => {
const ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
if (ns.length === 1 && text.startsWith("http")) {
- this.props.addDocument(Docs.Create.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }));// paste an image from its URL in the paste buffer
+ this.props.addDocument(Docs.Create.ImageDocument(text, { _nativeWidth: 300, _width: 300, x: x, y: y }));// paste an image from its URL in the paste buffer
} else {
this.pasteTable(ns, x, y);
}
});
} else if (!e.ctrlKey) {
this.props.addLiveTextDocument(
- Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }));
+ Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" }));
} else if (e.keyCode > 48 && e.keyCode <= 57) {
const notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
- const text = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" });
+ const text = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
text.layout = notes[(e.keyCode - 49) % notes.length];
this.props.addLiveTextDocument(text);
}
@@ -128,7 +129,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
let groupAttr: string | number = "";
const rowProto = new Doc();
rowProto.title = rowProto.Id;
- rowProto.width = 200;
+ rowProto._width = 200;
rowProto.isPrototype = true;
for (let i = 1; i < ns.length - 1; i++) {
const values = ns[i].split("\t");
@@ -144,10 +145,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
docDataProto.title = i.toString();
const doc = Doc.MakeDelegate(docDataProto);
- doc.width = 200;
+ doc._width = 200;
docList.push(doc);
}
- const newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group", "#f1efeb")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
+ const newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group", "#f1efeb")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", _width: 300, _height: 100 });
this.props.addDocument(newCol);
}
@@ -205,6 +206,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
MarqueeOptionsMenu.Instance.createCollection = this.collection;
MarqueeOptionsMenu.Instance.delete = this.delete;
MarqueeOptionsMenu.Instance.summarize = this.summary;
+ MarqueeOptionsMenu.Instance.inkToText = this.syntaxHighlight;
MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee;
MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
@@ -267,15 +269,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
get inkDoc() {
- return this.props.extensionDoc;
+ return this.props.Document;
}
get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored.
- return this.props.extensionDoc && Cast(this.props.extensionDoc.ink, InkField);
+ return Cast(this.props.Document.ink, InkField);
}
set ink(value: InkField | undefined) {
- this.props.extensionDoc && (this.props.extensionDoc.ink = value);
+ this.props.Document.ink = value;
}
@action
@@ -300,7 +302,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.hideMarquee();
}
- getCollection = (selected: Doc[]) => {
+ getCollection = (selected: Doc[], asTemplate: boolean) => {
const bounds = this.Bounds;
const defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
"rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
@@ -322,15 +324,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0);
const chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0];
// const inkData = this.ink ? this.ink.inkData : undefined;
- const newCollection = Docs.Create.FreeformDocument(selected, {
+ const creator = asTemplate ? Docs.Create.StackingDocument : Docs.Create.FreeformDocument;
+ const newCollection = creator(selected, {
x: bounds.left,
y: bounds.top,
- panX: 0,
- panY: 0,
+ _panX: 0,
+ _panY: 0,
backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
- width: bounds.width,
- height: bounds.height,
+ _width: bounds.width,
+ _height: bounds.height,
+ _LODdisable: true,
title: "a nested collection",
});
// const dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
@@ -353,7 +357,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
return d;
});
}
- const newCollection = this.getCollection(selected);
+ const newCollection = this.getCollection(selected, e.key === "t");
this.props.addDocument(newCollection);
this.props.selectDocuments([newCollection], []);
MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -361,6 +365,48 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
@action
+ syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ const selected = this.marqueeSelect(false);
+ if (e instanceof KeyboardEvent ? e.key === "i" : true) {
+ const inks = selected.filter(s => s.proto?.type === "ink");
+ const setDocs = selected.filter(s => s.proto?.type === "text" && s.color);
+ const sets = setDocs.map((sd) => {
+ return Cast(sd.data, RichTextField)?.Text as string;
+ });
+ const colors = setDocs.map(sd => FieldValue(sd.color) as string);
+ const wordToColor = new Map<string, string>();
+ sets.forEach((st: string, i: number) => {
+ const words = st.split(",");
+ words.forEach(word => {
+ wordToColor.set(word, colors[i]);
+ });
+ });
+ const inkFields = inks.map(i => Cast(i.data, InkField));
+ CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
+ const wordResults = results.filter((r: any) => r.category === "inkWord");
+ console.log(wordResults);
+ console.log(results);
+ for (const word of wordResults) {
+ const indices: number[] = word.strokeIds;
+ indices.forEach(i => {
+ if (wordToColor.has(word.recognizedText.toLowerCase())) {
+ inks[i].color = wordToColor.get(word.recognizedText.toLowerCase());
+ }
+ else {
+ for (const alt of word.alternates) {
+ if (wordToColor.has(alt.recognizedString.toLowerCase())) {
+ inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase());
+ break;
+ }
+ }
+ }
+ })
+ }
+ });
+ }
+ }
+
+ @action
summary = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const bounds = this.Bounds;
const selected = this.marqueeSelect(false);
@@ -373,16 +419,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
d.page = -1;
return d;
});
- newCollection.chromeStatus = "disabled";
- const summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
+ newCollection._chromeStatus = "disabled";
+ const summary = Docs.Create.TextDocument("", { x: bounds.left, y: bounds.top, _width: 300, _height: 100, _autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]);
newCollection.x = bounds.left + bounds.width;
Doc.GetProto(newCollection).summaryDoc = summary;
Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`);
if (e instanceof KeyboardEvent ? e.key === "s" : true) { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.
- const container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
- container.viewType = CollectionViewType.Stacking;
- container.autoHeight = true;
+ const container = Docs.Create.FreeformDocument([summary, newCollection], {
+ x: bounds.left, y: bounds.top, _width: 300, _height: 200, _autoHeight: true,
+ _viewType: CollectionViewType.Stacking, _chromeStatus: "disabled", title: "-summary-"
+ });
Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight"
this.props.addLiveTextDocument(container);
} else if (e instanceof KeyboardEvent ? e.key === "S" : false) { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them
@@ -405,12 +452,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.delete();
e.stopPropagation();
}
- if (e.key === "c" || e.key === "s" || e.key === "S") {
+ if (e.key === "c" || e.key === "t" || e.key === "s" || e.key === "S") {
this._commandExecuted = true;
e.stopPropagation();
e.preventDefault();
(e as any).propagationIsStopped = true;
- if (e.key === "c") {
+ if (e.key === "c" || e.key === "t") {
this.collection(e);
}
@@ -467,8 +514,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const layoutDoc = Doc.Layout(doc);
const x = NumCast(doc.x);
const y = NumCast(doc.y);
- const w = NumCast(layoutDoc.width);
- const h = NumCast(layoutDoc.height);
+ const w = NumCast(layoutDoc._width);
+ const h = NumCast(layoutDoc._height);
if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
selection.push(doc);
}
@@ -478,8 +525,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const layoutDoc = Doc.Layout(doc);
const x = NumCast(doc.x);
const y = NumCast(doc.y);
- const w = NumCast(layoutDoc.width);
- const h = NumCast(layoutDoc.height);
+ const w = NumCast(layoutDoc._width);
+ const h = NumCast(layoutDoc._height);
if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
selection.push(doc);
}
@@ -495,8 +542,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const layoutDoc = Doc.Layout(doc);
const x = NumCast(doc.x);
const y = NumCast(doc.y);
- const w = NumCast(layoutDoc.width);
- const h = NumCast(layoutDoc.height);
+ const w = NumCast(layoutDoc._width);
+ const h = NumCast(layoutDoc._height);
if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) {
selection.push(doc);
}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
new file mode 100644
index 000000000..f57ba438a
--- /dev/null
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
@@ -0,0 +1,33 @@
+.collectionMulticolumnView_contents {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+
+ .document-wrapper {
+ display: flex;
+ flex-direction: column;
+
+ .label-wrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ height: 20px;
+ }
+
+ }
+
+ .resizer {
+ cursor: ew-resize;
+ transition: 0.5s opacity ease;
+ display: flex;
+ flex-direction: column;
+
+ .internal {
+ width: 100%;
+ height: 100%;
+ transition: 0.5s background-color ease;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
new file mode 100644
index 000000000..041eb69da
--- /dev/null
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -0,0 +1,258 @@
+import { observer } from 'mobx-react';
+import { makeInterface } from '../../../../new_fields/Schema';
+import { documentSchema } from '../../../../new_fields/documentSchemas';
+import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
+import * as React from "react";
+import { Doc } from '../../../../new_fields/Doc';
+import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../new_fields/Types';
+import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
+import { Utils } from '../../../../Utils';
+import "./collectionMulticolumnView.scss";
+import { computed, trace, observable, action } from 'mobx';
+import { Transform } from '../../../util/Transform';
+import WidthLabel from './MulticolumnWidthLabel';
+import ResizeBar from './MulticolumnResizer';
+import { undoBatch } from '../../../util/UndoManager';
+import { DragManager } from '../../../util/DragManager';
+
+type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
+const MulticolumnDocument = makeInterface(documentSchema);
+
+interface WidthSpecifier {
+ magnitude: number;
+ unit: string;
+}
+
+interface LayoutData {
+ widthSpecifiers: WidthSpecifier[];
+ starSum: number;
+}
+
+export const WidthUnit = {
+ Pixel: "px",
+ Ratio: "*"
+};
+
+const resolvedUnits = Object.values(WidthUnit);
+const resizerWidth = 4;
+
+@observer
+export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) {
+
+ /**
+ * @returns the list of layout documents whose width unit is
+ * *, denoting that it will be displayed with a ratio, not fixed pixel, value
+ */
+ @computed
+ private get ratioDefinedDocs() {
+ return this.childLayoutPairs.map(({ layout }) => layout).filter(({ widthUnit }) => StrCast(widthUnit) === WidthUnit.Ratio);
+ }
+
+ /**
+ * This loops through all childLayoutPairs and extracts the values for widthUnit
+ * and widthMagnitude, ignoring any that are malformed. Additionally, it then
+ * normalizes the ratio values so that one * value is always 1, with the remaining
+ * values proportionate to that easily readable metric.
+ * @returns the list of the resolved width specifiers (unit and magnitude pairs)
+ * as well as the sum of the * coefficients, i.e. the ratio magnitudes
+ */
+ @computed
+ private get resolvedLayoutInformation(): LayoutData {
+ let starSum = 0;
+ const widthSpecifiers: WidthSpecifier[] = [];
+ this.childLayoutPairs.map(({ layout: { widthUnit, widthMagnitude } }) => {
+ const unit = StrCast(widthUnit);
+ const magnitude = NumCast(widthMagnitude);
+ if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
+ (unit === WidthUnit.Ratio) && (starSum += magnitude);
+ widthSpecifiers.push({ magnitude, unit });
+ }
+ /**
+ * Otherwise, the child document is ignored and the remaining
+ * space is allocated as if the document were absent from the child list
+ */
+ });
+
+ /**
+ * Here, since these values are all relative, adjustments during resizing or
+ * manual updating can, though their ratios remain the same, cause the values
+ * themselves to drift toward zero. Thus, whenever we change any of the values,
+ * we normalize everything (dividing by the smallest magnitude).
+ */
+ setTimeout(() => {
+ const { ratioDefinedDocs } = this;
+ if (this.childLayoutPairs.length) {
+ const minimum = Math.min(...ratioDefinedDocs.map(({ widthMagnitude }) => NumCast(widthMagnitude)));
+ if (minimum !== 0) {
+ ratioDefinedDocs.forEach(layout => layout.widthMagnitude = NumCast(layout.widthMagnitude) / minimum);
+ }
+ }
+ });
+
+ return { widthSpecifiers, starSum };
+ }
+
+ /**
+ * This returns the total quantity, in pixels, that this
+ * view needs to reserve for child documents that have
+ * (with higher priority) requested a fixed pixel width.
+ *
+ * If the underlying resolvedLayoutInformation returns null
+ * because we're waiting on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get totalFixedAllocation(): number | undefined {
+ return this.resolvedLayoutInformation?.widthSpecifiers.reduce(
+ (sum, { magnitude, unit }) => sum + (unit === WidthUnit.Pixel ? magnitude : 0), 0);
+ }
+
+ /**
+ * @returns the total quantity, in pixels, that this
+ * view needs to reserve for child documents that have
+ * (with lower priority) requested a certain relative proportion of the
+ * remaining pixel width not allocated for fixed widths.
+ *
+ * If the underlying totalFixedAllocation returns undefined
+ * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get totalRatioAllocation(): number | undefined {
+ const layoutInfoLen = this.resolvedLayoutInformation.widthSpecifiers.length;
+ if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
+ return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1));
+ }
+ }
+
+ /**
+ * @returns the total quantity, in pixels, that
+ * 1* (relative / star unit) is worth. For example,
+ * if the configuration has three documents, with, respectively,
+ * widths of 2*, 2* and 1*, and the panel width returns 1000px,
+ * this accessor returns 1000 / (2 + 2 + 1), or 200px.
+ * Elsewhere, this is then multiplied by each relative-width
+ * document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px).
+ *
+ * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
+ * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get columnUnitLength(): number | undefined {
+ if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
+ return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
+ }
+ }
+
+ /**
+ * This wrapper function exists to prevent mobx from
+ * needlessly rerendering the internal ContentFittingDocumentViews
+ */
+ private getColumnUnitLength = () => this.columnUnitLength;
+
+ /**
+ * @param layout the document whose transform we'd like to compute
+ * Given a layout document, this function
+ * returns the resolved width it has requested, in pixels.
+ * @returns the stored column width if already in pixels,
+ * or the ratio width evaluated to a pixel value
+ */
+ private lookupPixels = (layout: Doc): number => {
+ const columnUnitLength = this.columnUnitLength;
+ if (columnUnitLength === undefined) {
+ return 0; // we're still waiting on promises to resolve
+ }
+ let width = NumCast(layout.widthMagnitude);
+ if (StrCast(layout.widthUnit) === WidthUnit.Ratio) {
+ width *= columnUnitLength;
+ }
+ return width;
+ }
+
+ /**
+ * @returns the transform that will correctly place
+ * the document decorations box, shifted to the right by
+ * the sum of all the resolved column widths of the
+ * documents before the target.
+ */
+ private lookupIndividualTransform = (layout: Doc) => {
+ const columnUnitLength = this.columnUnitLength;
+ if (columnUnitLength === undefined) {
+ return Transform.Identity(); // we're still waiting on promises to resolve
+ }
+ let offset = 0;
+ for (const { layout: candidate } of this.childLayoutPairs) {
+ if (candidate === layout) {
+ return this.props.ScreenToLocalTransform().translate(-offset, 0);
+ }
+ offset += this.lookupPixels(candidate) + resizerWidth;
+ }
+ return Transform.Identity(); // type coersion, this case should never be hit
+ }
+
+ @undoBatch
+ @action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (super.drop(e, de)) {
+ de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
+ d.widthUnit = "*";
+ d.widthMagnitude = 1;
+ }));
+ }
+ return false;
+ }
+
+
+ @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+
+ /**
+ * @returns the resolved list of rendered child documents, displayed
+ * at their resolved pixel widths, each separated by a resizer.
+ */
+ @computed
+ private get contents(): JSX.Element[] | null {
+ const { childLayoutPairs } = this;
+ const { Document, PanelHeight } = this.props;
+ const collector: JSX.Element[] = [];
+ for (let i = 0; i < childLayoutPairs.length; i++) {
+ const { layout } = childLayoutPairs[i];
+ collector.push(
+ <div
+ className={"document-wrapper"}
+ key={Utils.GenerateGuid()}
+ >
+ <ContentFittingDocumentView
+ {...this.props}
+ Document={layout}
+ DataDocument={layout.resolvedDataDoc as Doc}
+ CollectionDoc={this.props.Document}
+ PanelWidth={() => this.lookupPixels(layout)}
+ PanelHeight={() => PanelHeight() - (BoolCast(Document.showWidthLabels) ? 20 : 0)}
+ getTransform={() => this.lookupIndividualTransform(layout)}
+ onClick={this.onChildClickHandler}
+ />
+ <WidthLabel
+ layout={layout}
+ collectionDoc={Document}
+ />
+ </div>,
+ <ResizeBar
+ width={resizerWidth}
+ key={Utils.GenerateGuid()}
+ columnUnitLength={this.getColumnUnitLength}
+ toLeft={layout}
+ toRight={childLayoutPairs[i + 1]?.layout}
+ />
+ );
+ }
+ collector.pop(); // removes the final extraneous resize bar
+ return collector;
+ }
+
+ render(): JSX.Element {
+ return (
+ <div className={"collectionMulticolumnView_contents"} ref={this.createDashEventsTarget}>
+ {this.contents}
+ </div>
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
new file mode 100644
index 000000000..11e210958
--- /dev/null
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -0,0 +1,116 @@
+import * as React from "react";
+import { observer } from "mobx-react";
+import { observable, action } from "mobx";
+import { Doc } from "../../../../new_fields/Doc";
+import { NumCast, StrCast } from "../../../../new_fields/Types";
+import { WidthUnit } from "./CollectionMulticolumnView";
+
+interface ResizerProps {
+ width: number;
+ columnUnitLength(): number | undefined;
+ toLeft?: Doc;
+ toRight?: Doc;
+}
+
+enum ResizeMode {
+ Global = "blue",
+ Pinned = "red",
+ Undefined = "black"
+}
+
+const resizerOpacity = 1;
+
+@observer
+export default class ResizeBar extends React.Component<ResizerProps> {
+ @observable private isHoverActive = false;
+ @observable private isResizingActive = false;
+ @observable private resizeMode = ResizeMode.Undefined;
+
+ @action
+ private registerResizing = (e: React.PointerEvent<HTMLDivElement>, mode: ResizeMode) => {
+ e.stopPropagation();
+ e.preventDefault();
+ this.resizeMode = mode;
+ window.removeEventListener("pointermove", this.onPointerMove);
+ window.removeEventListener("pointerup", this.onPointerUp);
+ window.addEventListener("pointermove", this.onPointerMove);
+ window.addEventListener("pointerup", this.onPointerUp);
+ this.isResizingActive = true;
+ }
+
+ private onPointerMove = ({ movementX }: PointerEvent) => {
+ const { toLeft, toRight, columnUnitLength } = this.props;
+ const movingRight = movementX > 0;
+ const toNarrow = movingRight ? toRight : toLeft;
+ const toWiden = movingRight ? toLeft : toRight;
+ const unitLength = columnUnitLength();
+ if (unitLength) {
+ if (toNarrow) {
+ const { widthUnit, widthMagnitude } = toNarrow;
+ const scale = widthUnit === WidthUnit.Ratio ? unitLength : 1;
+ toNarrow.widthMagnitude = NumCast(widthMagnitude) - Math.abs(movementX) / scale;
+ }
+ if (this.resizeMode === ResizeMode.Pinned && toWiden) {
+ const { widthUnit, widthMagnitude } = toWiden;
+ const scale = widthUnit === WidthUnit.Ratio ? unitLength : 1;
+ toWiden.widthMagnitude = NumCast(widthMagnitude) + Math.abs(movementX) / scale;
+ }
+ }
+ }
+
+ private get isActivated() {
+ const { toLeft, toRight } = this.props;
+ if (toLeft && toRight) {
+ if (StrCast(toLeft.widthUnit) === WidthUnit.Pixel && StrCast(toRight.widthUnit) === WidthUnit.Pixel) {
+ return false;
+ }
+ return true;
+ } else if (toLeft) {
+ if (StrCast(toLeft.widthUnit) === WidthUnit.Pixel) {
+ return false;
+ }
+ return true;
+ } else if (toRight) {
+ if (StrCast(toRight.widthUnit) === WidthUnit.Pixel) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @action
+ private onPointerUp = () => {
+ this.resizeMode = ResizeMode.Undefined;
+ this.isResizingActive = false;
+ this.isHoverActive = false;
+ window.removeEventListener("pointermove", this.onPointerMove);
+ window.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ render() {
+ return (
+ <div
+ className={"resizer"}
+ style={{
+ width: this.props.width,
+ opacity: this.isActivated && this.isHoverActive ? resizerOpacity : 0
+ }}
+ onPointerEnter={action(() => this.isHoverActive = true)}
+ onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}
+ >
+ <div
+ className={"internal"}
+ onPointerDown={e => this.registerResizing(e, ResizeMode.Pinned)}
+ style={{ backgroundColor: this.resizeMode }}
+ />
+ <div
+ className={"internal"}
+ onPointerDown={e => this.registerResizing(e, ResizeMode.Global)}
+ style={{ backgroundColor: this.resizeMode }}
+ />
+ </div>
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
new file mode 100644
index 000000000..b394fed62
--- /dev/null
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
@@ -0,0 +1,56 @@
+import * as React from "react";
+import { observer } from "mobx-react";
+import { computed } from "mobx";
+import { Doc } from "../../../../new_fields/Doc";
+import { NumCast, StrCast, BoolCast } from "../../../../new_fields/Types";
+import { EditableView } from "../../EditableView";
+import { WidthUnit } from "./CollectionMulticolumnView";
+
+interface WidthLabelProps {
+ layout: Doc;
+ collectionDoc: Doc;
+ decimals?: number;
+}
+
+@observer
+export default class WidthLabel extends React.Component<WidthLabelProps> {
+
+ @computed
+ private get contents() {
+ const { layout, decimals } = this.props;
+ const getUnit = () => StrCast(layout.widthUnit);
+ const getMagnitude = () => String(+NumCast(layout.widthMagnitude).toFixed(decimals ?? 3));
+ return (
+ <div className={"label-wrapper"}>
+ <EditableView
+ GetValue={getMagnitude}
+ SetValue={value => {
+ const converted = Number(value);
+ if (!isNaN(converted) && converted > 0) {
+ layout.widthMagnitude = converted;
+ return true;
+ }
+ return false;
+ }}
+ contents={getMagnitude()}
+ />
+ <EditableView
+ GetValue={getUnit}
+ SetValue={value => {
+ if (Object.values(WidthUnit).includes(value)) {
+ layout.widthUnit = value;
+ return true;
+ }
+ return false;
+ }}
+ contents={getUnit()}
+ />
+ </div>
+ );
+ }
+
+ render() {
+ return BoolCast(this.props.collectionDoc.showWidthLabels) ? this.contents : (null);
+ }
+
+} \ No newline at end of file