aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx18
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx12
-rw-r--r--src/client/views/collections/CollectionStackingView.scss8
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx10
-rw-r--r--src/client/views/collections/CollectionSubView.tsx8
-rw-r--r--src/client/views/collections/CollectionTreeView.scss3
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx399
-rw-r--r--src/client/views/collections/CollectionView.scss14
-rw-r--r--src/client/views/collections/CollectionView.tsx9
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx32
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx20
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx5
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss7
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx147
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss7
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx236
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx29
17 files changed, 603 insertions, 361 deletions
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index a969e302d..79c577b6d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -11,7 +11,7 @@ import { Id } from '../../../fields/FieldSymbols';
import { FieldId } from "../../../fields/RefField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter } from "../../../Utils";
+import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter, setupMoveUpEvents, returnFalse } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
@@ -521,13 +521,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
tab.setActive(true);
};
const onDown = (e: React.PointerEvent) => {
- if (!(e.nativeEvent as any).defaultPrevented) {
- e.preventDefault();
- e.stopPropagation();
- const dragData = new DragManager.DocumentDragData([doc]);
- dragData.dropAction = doc.dropAction as dropActionType;
- DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
- }
+ setupMoveUpEvents(this, e, (e) => {
+ if (!(e as any).defaultPrevented) {
+ const dragData = new DragManager.DocumentDragData([doc]);
+ dragData.dropAction = doc.dropAction as dropActionType;
+ DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
+ return true;
+ }
+ return false;
+ }, returnFalse, emptyFunction);
};
tab.buttonDisposer = reaction(() => ((view: Opt<DocumentView>) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)),
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index cc7a9f5ac..e0b53e762 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -43,6 +43,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@observable private heading: string = "";
@observable private color: string = "#f1efeb";
@observable private collapsed: boolean = false;
+ @observable private _paletteOn = false;
private set _heading(value: string) { runInAction(() => this.props.headingObject && (this.props.headingObject.heading = this.heading = value)); }
private set _color(value: string) { runInAction(() => this.props.headingObject && (this.props.headingObject.color = this.color = value)); }
private set _collapsed(value: boolean) { runInAction(() => this.props.headingObject && (this.props.headingObject.collapsed = this.collapsed = value)); }
@@ -293,11 +294,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
{noChrome ? evContents : <EditableView {...headerEditableViewProps} />}
{noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionColor">
- <Flyout anchorPoint={anchorPoints.CENTER_RIGHT} content={this.renderColorPicker()}>
- <button className="collectionStackingView-sectionColorButton">
- <FontAwesomeIcon icon="palette" size="lg" />
- </button>
- </ Flyout >
+ <button className="collectionStackingView-sectionColorButton" onClick={action(e => this._paletteOn = !this._paletteOn)}>
+ <FontAwesomeIcon icon="palette" size="lg" />
+ </button>
+ {this._paletteOn ? this.renderColorPicker() : (null)}
</div>
}
{noChrome ? (null) : <button className="collectionStackingView-sectionDelete" onClick={noChrome ? undefined : this.collapseSection}>
@@ -305,7 +305,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
</button>}
{noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionOptions">
- <Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}>
+ <Flyout anchorPoint={anchorPoints.TOP_CENTER} content={this.renderMenu()}>
<button className="collectionStackingView-sectionOptionButton">
<FontAwesomeIcon icon="ellipsis-v" size="lg" />
</button>
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 203c51163..3d8ec2fd5 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -240,11 +240,15 @@
}
.collectionStackingView-sectionColorButton {
- height: 35px;
+ height: 30px;
+ display: inherit;
}
.collectionStackingView-colorPicker {
width: 78px;
+ z-index: 10;
+ position: relative;
+ background: white;
.colorOptions {
display: flex;
@@ -278,7 +282,7 @@
}
.collectionStackingView-sectionOptionButton {
- height: 35px;
+ height: 30px;
}
.collectionStackingView-optionPicker {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index bcd55f0fe..b60ed853b 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -50,6 +50,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
private dropDisposer?: DragManager.DragDropDisposer;
private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
+ @observable _paletteOn = false;
@observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
@observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
_ele: HTMLElement | null = null;
@@ -326,11 +327,10 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
<EditableView {...headerEditableViewProps} />
{evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionColor">
- <Flyout anchorPoint={anchorPoints.CENTER_RIGHT} content={this.renderColorPicker()}>
- <button className="collectionStackingView-sectionColorButton">
- <FontAwesomeIcon icon="palette" size="lg" />
- </button>
- </ Flyout >
+ <button className="collectionStackingView-sectionColorButton" onClick={action(e => this._paletteOn = !this._paletteOn)}>
+ <FontAwesomeIcon icon="palette" size="lg" />
+ </button>
+ {this._paletteOn ? this.renderColorPicker() : (null)}
</div>
}
{evContents === `NO ${key.toUpperCase()} VALUE` ?
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 00d6d59c8..3a13ac822 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -19,6 +19,7 @@ import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocComponent } from "../DocComponent";
import { FieldViewProps } from "../nodes/FieldView";
import React = require("react");
+import * as rp from 'request-promise';
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc | Doc[]) => boolean;
@@ -103,8 +104,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
docFilters = () => {
return this.props.ignoreFields?.includes("_docFilters") ? [] :
- this.props.docFilters !== returnEmptyFilter ? this.props.docFilters() :
- Cast(this.props.Document._docFilters, listSpec("string"), []);
+ [...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])];
}
@computed get childDocs() {
const docFilters = this.docFilters();
@@ -400,9 +400,9 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const item = e.dataTransfer.items[i];
if (item.kind === "string" && item.type.includes("uri")) {
const stringContents = await new Promise<string>(resolve => item.getAsString(resolve));
- const type = "html";// (await rp.head(Utils.CorsProxy(stringContents)))["content-type"];
+ const type = (await rp.head(Utils.CorsProxy(stringContents)))["content-type"];
if (type) {
- const doc = await DocUtils.DocumentFromType(type, stringContents, options);
+ const doc = await DocUtils.DocumentFromType(type, Utils.CorsProxy(stringContents), options);
doc && generatedDocuments.push(doc);
}
}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 2aac81146..50f0534bd 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -82,8 +82,6 @@
text-overflow: ellipsis;
white-space: pre-wrap;
min-width: 10px;
- // width:100%;//width: max-content;
-
}
.treeViewItem-openRight {
@@ -100,6 +98,7 @@
border-left: dashed 1px #00000042;
}
+.treeViewItem-header-editing,
.treeViewItem-header {
border: transparent 1px solid;
display: flex;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 747eb36a1..d54f4d6e6 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -13,7 +13,7 @@ import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from '../../util/DocumentManager';
import { SnappingManager } from '../../util/SnappingManager';
-import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
import { Scripting } from '../../util/Scripting';
import { SelectionManager } from '../../util/SelectionManager';
import { Transform } from '../../util/Transform';
@@ -55,7 +55,7 @@ export interface TreeViewProps {
ScreenToLocalTransform: () => Transform;
backgroundColor?: (doc: Doc) => string | undefined;
outerXf: () => { translateX: number, translateY: number };
- treeViewId: Doc;
+ treeViewDoc: Doc;
parentKey: string;
active: (outsideReaction?: boolean) => boolean;
treeViewHideHeaderFields: () => boolean;
@@ -76,31 +76,32 @@ export interface TreeViewProps {
* treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
*/
class TreeView extends React.Component<TreeViewProps> {
- static _editTitleScript: ScriptField | undefined;
+ private _editTitleScript: ScriptField | undefined;
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
private _tref = React.createRef<HTMLDivElement>();
private _docRef = React.createRef<DocumentView>();
+ private _uniqueId = Utils.GenerateGuid();
+ private _editMaxWidth: number | string = 0;
+ get doc() { return this.props.document; }
get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode, false); }
- get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive
- get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, this.noviceMode ? "layout" : "fields"); }
+ get displayName() { return "TreeView(" + this.doc.title + ")"; } // this makes mobx trace() statements more descriptive
+ get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.doc.defaultExpandedView, this.noviceMode ? "layout" : "fields"); }
@observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
- set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
- @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.props.document.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
- @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
+ set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.doc.treeViewOpen = this._overrideTreeViewOpen = c; }
+ @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && BoolCast(this.doc.treeViewOpen)) || this._overrideTreeViewOpen; }
+ @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.defaultExpandedView); }
@computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); }
- @computed get dataDoc() { return this.props.document[DataSym]; }
- @computed get fieldKey() {
- const splits = StrCast(Doc.LayoutField(this.props.document)).split("fieldKey={\'");
- return splits.length > 1 ? splits[1].split("\'")[0] : "data";
- }
+ @computed get dataDoc() { return this.doc[DataSym]; }
+ @computed get layoutDoc() { return Doc.Layout(this.doc); }
+ @computed get fieldKey() { const splits = StrCast(Doc.LayoutField(this.doc)).split("fieldKey={\'"); return splits.length > 1 ? splits[1].split("\'")[0] : "data"; }
childDocList(field: string) {
- const layout = Doc.LayoutField(this.props.document) instanceof Doc ? Doc.LayoutField(this.props.document) as Doc : undefined;
+ const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined;
return ((this.props.dataDoc ? DocListCast(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field
(layout ? Cast(layout[field], listSpec(Doc)) : undefined) || // else if there's a layout doc, display it's fields
- Cast(this.props.document[field], listSpec(Doc))) as Doc[]; // otherwise use the document's data field
+ Cast(this.doc[field], listSpec(Doc))) as Doc[]; // otherwise use the document's data field
}
@computed get childDocs() { return this.childDocList(this.fieldKey); }
@computed get childLinks() { return this.childDocList("links"); }
@@ -109,22 +110,27 @@ class TreeView extends React.Component<TreeViewProps> {
Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey]));
}
- @undoBatch openRight = () => this.props.addDocTab(this.props.document, "onRight", this.props.libraryPath);
+ @undoBatch openRight = () => this.props.addDocTab(this.doc, "onRight", this.props.libraryPath);
@undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
- return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
+ return this.doc !== target && this.props.deleteDoc(doc) && addDoc(doc);
}
@undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
- return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) =>
- flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
+ return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
}
@undoBatch @action removeDoc = (doc: Doc | Doc[]) => {
return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) =>
flg && Doc.RemoveDocFromList(this.props.containingCollection, Doc.LayoutFieldKey(this.props.containingCollection), doc), true);
}
+ constructor(props: any) {
+ super(props);
+ this._editTitleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); selectDoc(self);} `);
+ if (Doc.GetT(this.doc, "editTitle", "string", true) === "*") Doc.SetInPlace(this.doc, "editTitle", this._uniqueId, false);
+ }
+
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer?.();
- ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this)), this.props.document);
+ ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this)), this.doc);
}
onPointerEnter = (e: React.PointerEvent): void => {
@@ -136,7 +142,9 @@ class TreeView extends React.Component<TreeViewProps> {
}
onPointerLeave = (e: React.PointerEvent): void => {
Doc.UnBrushDoc(this.dataDoc);
- this._header!.current!.className = "treeViewItem-header";
+ if (this._header?.current?.className !== "treeViewItem-header-editing") {
+ this._header!.current!.className = "treeViewItem-header";
+ }
document.removeEventListener("pointermove", this.onDragMove, true);
}
onDragMove = (e: PointerEvent): void => {
@@ -144,7 +152,7 @@ class TreeView extends React.Component<TreeViewProps> {
const pt = [e.clientX, e.clientY];
const rect = this._header!.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && DocListCast(this.dataDoc[this.fieldKey]).length);
+ const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length);
this._header!.current!.className = "treeViewItem-header";
if (inside) this._header!.current!.className += " treeViewItem-header-inside";
else if (before) this._header!.current!.className += " treeViewItem-header-above";
@@ -155,42 +163,38 @@ class TreeView extends React.Component<TreeViewProps> {
editableView = (key: string, style?: string) => (<EditableView
oneLine={true}
display={"inline-block"}
- editing={true /*this.dataDoc[Id] === EditableView.loadId*/}
- contents={StrCast(this.props.document[key])}
+ editing={true}
+ contents={StrCast(this.doc[key])}
height={12}
+ sizeToContent={true}
fontStyle={style}
fontSize={12}
- GetValue={() => StrCast(this.props.document[key])}
+ GetValue={() => StrCast(this.doc[key])}
SetValue={undoBatch((value: string) => {
- Doc.SetInPlace(this.props.document, key, value, false) || true;
- Doc.SetInPlace(this.props.document, "editTitle", undefined, false);
+ Doc.SetInPlace(this.doc, key, value, false) || true;
+ Doc.SetInPlace(this.doc, "editTitle", undefined, false);
})}
OnFillDown={undoBatch((value: string) => {
- Doc.SetInPlace(this.props.document, key, value, false);
+ Doc.SetInPlace(this.doc, key, value, false);
const doc = Docs.Create.FreeformDocument([], { title: "-", x: 0, y: 0, _width: 100, _height: 25, _LODdisable: true, templates: new List<string>([Templates.Title.Layout]) });
- Doc.SetInPlace(this.props.document, "editTitle", undefined, false);
- Doc.SetInPlace(doc, "editTitle", true, false);
+ Doc.SetInPlace(this.doc, "editTitle", undefined, false);
+ Doc.SetInPlace(doc, "editTitle", "*", false);
return this.props.addDocument(doc);
})}
onClick={() => {
SelectionManager.DeselectAll();
- Doc.UserDoc().activeSelection = new List([this.props.document]);
+ Doc.UserDoc().activeSelection = new List([this.doc]);
return false;
}}
OnTab={undoBatch((shift?: boolean) => {
- EditableView.loadId = this.dataDoc[Id];
shift ? this.props.outdentDocument?.() : this.props.indentDocument?.();
- setTimeout(() => { // unsetting/setting brushing for this doc will recreate & refocus this editableView after all other treeview changes have been made to the Dom (which may remove focus from this document).
- Doc.UnBrushDoc(this.props.document);
- Doc.BrushDoc(this.props.document);
- EditableView.loadId = "";
- }, 0);
+ setTimeout(() => Doc.SetInPlace(this.doc, "editTitle", "*", false), 0);
})}
/>)
preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
const dragData = de.complete.docDragData;
- dragData && (dragData.dropAction = this.props.treeViewId[Id] === dragData.treeViewId ? "same" : dragData.dropAction);
+ dragData && (dragData.dropAction = this.props.treeViewDoc === dragData.treeViewDoc ? "same" : dragData.dropAction);
}
@undoBatch
@@ -198,18 +202,18 @@ class TreeView extends React.Component<TreeViewProps> {
const pt = [de.x, de.y];
const rect = this._header!.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && DocListCast(this.dataDoc[this.fieldKey]).length);
+ const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length);
const complete = de.complete;
if (complete.linkDragData) {
const sourceDoc = complete.linkDragData.linkSourceDocument;
- const destDoc = this.props.document;
+ const destDoc = this.doc;
DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link");
e.stopPropagation();
}
const docDragData = complete.docDragData;
if (docDragData) {
e.stopPropagation();
- if (docDragData.draggedDocuments[0] === this.props.document) return true;
+ if (docDragData.draggedDocuments[0] === this.doc) return true;
const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
let addDoc = parentAddDoc;
if (inside) {
@@ -222,35 +226,28 @@ class TreeView extends React.Component<TreeViewProps> {
return false;
}
- docTransform = () => {
- const { scale, translateX, translateY } = Utils.GetScreenTransform(this._dref.current!);
- const outerXf = this.props.outerXf();
- const offset = this.props.ScreenToLocalTransform().transformDirection((outerXf.translateX - translateX), outerXf.translateY - translateY);
- const finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
-
- return finalXf;
- }
- getTransform = () => {
- const { scale, translateX, translateY } = Utils.GetScreenTransform(this._tref.current!);
+ refTransform = (ref: HTMLDivElement) => {
+ const { scale, translateX, translateY } = Utils.GetScreenTransform(ref);
const outerXf = this.props.outerXf();
const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
- const finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
- return finalXf;
+ return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
}
+ docTransform = () => this.refTransform(this._dref.current!);
+ getTransform = () => this.refTransform(this._tref.current!);
docWidth = () => {
- const layoutDoc = Doc.Layout(this.props.document);
+ const layoutDoc = this.layoutDoc;
const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
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;
}
docHeight = () => {
- const layoutDoc = Doc.Layout(this.props.document);
+ const layoutDoc = this.layoutDoc;
const bounds = this.boundsOfCollectionDocument;
return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => {
const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
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) :
+ return layoutDoc._fitWidth ? (!this.doc._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;
@@ -259,7 +256,7 @@ class TreeView extends React.Component<TreeViewProps> {
@computed get expandedField() {
const ids: { [key: string]: string } = {};
- const doc = this.props.document;
+ const doc = this.doc;
doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
const rows: JSX.Element[] = [];
@@ -273,13 +270,12 @@ class TreeView extends React.Component<TreeViewProps> {
const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce(
(flg, doc) => flg && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true), true);
contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
- DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
+ DocListCast(contents), this.props.treeViewDoc, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
[...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields);
} else {
- contentElement = <EditableView
- key="editableView"
+ contentElement = <EditableView key="editableView"
contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
height={13}
fontSize={12}
@@ -307,45 +303,45 @@ class TreeView extends React.Component<TreeViewProps> {
return rows;
}
- rtfWidth = () => Math.min(Doc.Layout(this.props.document)?.[WidthSym](), this.props.panelWidth() - 20);
- rtfHeight = () => this.rtfWidth() < Doc.Layout(this.props.document)?.[WidthSym]() ? Math.min(Doc.Layout(this.props.document)?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+ rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.props.panelWidth() - 20);
+ rtfHeight = () => this.rtfWidth() < this.layoutDoc?.[WidthSym]() ? Math.min(this.layoutDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
@computed get renderContent() {
TraceMobx();
- const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
- if (expandKey !== undefined) {
+ const expandKey = this.treeViewExpandedView;
+ if (["links", this.fieldKey].includes(expandKey)) {
const remDoc = (doc: Doc | Doc[]) => this.remove(doc, expandKey);
const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) =>
(doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true), true);
const docs = expandKey === "links" ? this.childLinks : this.childDocs;
const sortKey = `${this.fieldKey}-sortAscending`;
return <ul key={expandKey + "more"} onClick={(e) => {
- this.props.document[sortKey] = (this.props.document[sortKey] ? false : (this.props.document[sortKey] === false ? undefined : true));
+ this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true));
e.stopPropagation();
}}>
{!docs ? (null) :
- TreeView.GetChildElements(docs, this.props.treeViewId, Doc.Layout(this.props.document),
+ TreeView.GetChildElements(docs, this.props.treeViewDoc, this.layoutDoc,
this.dataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
- StrCast(this.props.document.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
+ StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
- [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields)}
+ [...this.props.renderedIds, this.doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields)}
</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}>
+ return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.doc[Id] + this.doc.title}>
{this.expandedField}
</div></ul>;
} else {
- const layoutDoc = Doc.Layout(this.props.document);
+ const layoutDoc = this.layoutDoc;
const panelHeight = layoutDoc.type === DocumentType.RTF ? this.rtfHeight : this.docHeight;
const panelWidth = layoutDoc.type === DocumentType.RTF ? this.rtfWidth : this.docWidth;
- return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.props.document[Id] + this.props.document.title}>
+ return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.doc[Id]}>
<ContentFittingDocumentView
Document={layoutDoc}
DataDoc={this.dataDoc}
LibraryPath={emptyPath}
renderDepth={this.props.renderDepth + 1}
rootSelected={returnTrue}
- treeViewId={this.props.treeViewId[Id]}
+ treeViewDoc={undefined}
backgroundColor={this.props.backgroundColor}
fitToBox={this.boundsOfCollectionDocument !== undefined}
FreezeDimensions={true}
@@ -372,17 +368,17 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
- get onCheckedClick() { return this.props.onCheckedClick || ScriptCast(this.props.document.onCheckedClick); }
+ get onCheckedClick() { return this.props.onCheckedClick || ScriptCast(this.doc.onCheckedClick); }
@action
bulletClick = (e: React.MouseEvent) => {
- if (this.onCheckedClick && this.props.document.type !== DocumentType.COL) {
+ if (this.onCheckedClick && this.doc.type !== DocumentType.COL) {
// this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check";
this.onCheckedClick.script.run({
- this: this.props.document.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.props.document,
+ this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
heading: this.props.containingCollection.title,
- checked: this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check",
- containingTreeView: this.props.treeViewId,
+ checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? undefined : "check",
+ containingTreeView: this.props.treeViewDoc,
}, console.log);
} else {
this.treeViewOpen = !this.treeViewOpen;
@@ -392,104 +388,110 @@ class TreeView extends React.Component<TreeViewProps> {
@computed
get renderBullet() {
- const checked = this.props.document.type === DocumentType.COL ? undefined : this.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined;
+ const checked = this.doc.type === DocumentType.COL ? undefined : this.onCheckedClick ? (this.doc.treeViewChecked ?? "unchecked") : undefined;
return <div className="bullet"
title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"}
onClick={this.bulletClick}
- style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 0.4 }}>
+ style={{ color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 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>;
}
showContextMenu = (e: React.MouseEvent) => {
this._docRef.current?.ContentDiv && simulateMouseClick(this._docRef.current.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
- e.stopPropagation();
}
focusOnDoc = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(doc)?.props.focus(doc, true);
- contextMenuItems = () => {
- const focusScript = ScriptField.MakeFunction(`DocFocus(self)`);
- return [{ script: focusScript!, label: "Focus" }];
- }
+ contextMenuItems = () => [{ script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }];
+ truncateTitleWidth = () => NumCast(this.props.treeViewDoc.treeViewTruncateTitleWidth, 0);
+ showTitleEdit = () => ["*", this._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || "");
/**
* Renders the EditableView title element for placement into the tree.
*/
@computed
get renderTitle() {
TraceMobx();
- (!TreeView._editTitleScript) && (TreeView._editTitleScript = ScriptField.MakeFunction("setInPlace(self, 'editTitle', true)"));
- const headerElements = (
+ const headerElements = this.props.treeViewHideHeaderFields() ? (null) :
<>
- <FontAwesomeIcon icon="cog" size="sm" onClick={e => this.showContextMenu(e)}></FontAwesomeIcon>
+ <FontAwesomeIcon icon="cog" size="sm" onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} />
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
onPointerDown={action(() => {
if (this.treeViewOpen) {
- this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode ? "layout" : "fields") :
- this.treeViewExpandedView === "fields" && Doc.Layout(this.props.document) ? "layout" :
- this.treeViewExpandedView === "layout" && this.props.document.links ? "links" :
+ this.doc.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode ? "layout" : "fields") :
+ this.treeViewExpandedView === "fields" && this.layoutDoc ? "layout" :
+ this.treeViewExpandedView === "layout" && DocListCast(this.doc.links).length ? "links" :
this.childDocs ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields");
}
this.treeViewOpen = true;
})}>
{this.treeViewExpandedView}
</span>
- </>);
- const openRight = (<div className="treeViewItem-openRight" onClick={this.openRight}>
- <FontAwesomeIcon title="open in pane on right" icon="angle-right" size="lg" />
- </div>);
+ </>;
+ const view = this.showTitleEdit() ? this.editableView("title") :
+ <DocumentView
+ ref={this._docRef}
+ Document={this.doc}
+ DataDoc={undefined}
+ treeViewDoc={this.props.treeViewDoc}
+ LibraryPath={this.props.libraryPath || emptyPath}
+ addDocument={undefined}
+ addDocTab={this.props.addDocTab}
+ rootSelected={returnTrue}
+ pinToPres={emptyFunction}
+ onClick={this.props.onChildClick || this._editTitleScript}
+ dropAction={this.props.dropAction}
+ moveDocument={this.move}
+ removeDocument={this.removeDoc}
+ ScreenToLocalTransform={this.getTransform}
+ ContentScaling={returnOne}
+ PanelWidth={this.truncateTitleWidth}
+ PanelHeight={returnZero}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ contextMenuItems={this.contextMenuItems}
+ renderDepth={1}
+ focus={returnTrue}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ dontRegisterView={BoolCast(this.props.treeViewDoc.dontRegisterChildViews)}
+ docFilters={returnEmptyFilter}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={this.props.containingCollection}
+ />;
return <>
<div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`}
style={{
- fontWeight: this.props.document.searchMatch ? "bold" : undefined,
- textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined,
- outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
+ fontWeight: this.doc.searchMatch ? "bold" : undefined,
+ textDecoration: Doc.GetT(this.doc, "title", "string", true) ? "underline" : undefined,
+ outline: BoolCast(this.doc.workspaceBrush) ? "dashed 1px #06123232" : undefined,
pointerEvents: this.props.active() || SnappingManager.GetIsDragging() ? undefined : "none"
}} >
- {Doc.GetT(this.props.document, "editTitle", "boolean", true) ?
- this.editableView("title") :
- <DocumentView
- ref={this._docRef}
- Document={this.props.document}
- DataDoc={undefined}
- treeViewId={this.props.treeViewId[Id]}
- LibraryPath={this.props.libraryPath || emptyPath}
- addDocument={undefined}
- addDocTab={this.props.addDocTab}
- rootSelected={returnTrue}
- pinToPres={emptyFunction}
- onClick={this.props.onChildClick || TreeView._editTitleScript}
- dropAction={this.props.dropAction}
- moveDocument={this.move}
- removeDocument={this.removeDoc}
- ScreenToLocalTransform={this.getTransform}
- ContentScaling={returnOne}
- PanelWidth={returnZero}
- PanelHeight={returnZero}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- contextMenuItems={this.contextMenuItems}
- renderDepth={1}
- focus={returnTrue}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildViews)}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={this.props.containingCollection}
- />}
+ {view}
</div >
- {this.props.treeViewHideHeaderFields() ? (null) : headerElements}
- {openRight}
+ {headerElements}
+ <div className="treeViewItem-openRight" onClick={this.openRight}>
+ <FontAwesomeIcon title="open in pane on right" icon="external-link-alt" size="sm" />
+ </div>
</>;
}
render() {
TraceMobx();
- const sorting = this.props.document[`${this.fieldKey}-sortAscending`];
- //setTimeout(() => runInAction(() => untracked(() => this._overrideTreeViewOpen = this.treeViewOpen)), 0);
+ const sorting = this.doc[`${this.fieldKey}-sortAscending`];
+ if (this.showTitleEdit()) { // find containing CollectionTreeView and set our maximum width so the containing tree view won't have to scroll
+ let par: any = this._header?.current;
+ if (par) {
+ while (par && par.className !== "collectionTreeView-dropTarget") par = par.parentNode;
+ if (par) {
+ const par_rect = (par as HTMLElement).getBoundingClientRect();
+ const my_recct = this._docRef.current?.ContentDiv?.getBoundingClientRect();
+ this._editMaxWidth = Math.max(100, par_rect.right - (my_recct?.left || 0));
+ }
+ }
+ } else this._editMaxWidth = "";
return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active() && SelectionManager.DeselectAll()}>
<li className="collection-child">
- <div className="treeViewItem-header" ref={this._header} onClick={e => {
+ <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }} onClick={e => {
if (this.props.active(true)) {
e.stopPropagation();
e.preventDefault();
@@ -507,14 +509,14 @@ class TreeView extends React.Component<TreeViewProps> {
{this.renderTitle}
</div>
<div className="treeViewItem-border" style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}>
- {!this.treeViewOpen || this.props.renderedIds.indexOf(this.props.document[Id]) !== -1 ? (null) : this.renderContent}
+ {!this.treeViewOpen || this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? (null) : this.renderContent}
</div>
</li>
</div>;
}
public static GetChildElements(
childDocs: Doc[],
- treeViewId: Doc,
+ treeViewDoc: Doc,
containingCollection: Doc,
dataDoc: Doc | undefined,
key: string,
@@ -624,7 +626,7 @@ class TreeView extends React.Component<TreeViewProps> {
libraryPath={libraryPath ? [...libraryPath, containingCollection] : undefined}
containingCollection={containingCollection}
prevSibling={docs[i]}
- treeViewId={treeViewId}
+ treeViewDoc={treeViewDoc}
key={child[Id]}
indentDocument={indent}
outdentDocument={outdent}
@@ -665,21 +667,22 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
private treedropDisposer?: DragManager.DragDropDisposer;
private _mainEle?: HTMLDivElement;
- @computed get dataDoc() { return this.props.DataDoc || this.props.Document; }
+ @computed get doc() { return this.props.Document; }
+ @computed get dataDoc() { return this.props.DataDoc || this.doc; }
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this.treedropDisposer?.();
if (this._mainEle = ele) {
- this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.props.Document, this.onInternalPreDrop.bind(this));
+ this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this));
}
}
protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
const dragData = de.complete.docDragData;
if (dragData) {
- if (targetAction && !dragData.draggedDocuments.some(d => d.context === this.props.Document && this.childDocs.includes(d))) {
+ if (targetAction && !dragData.draggedDocuments.some(d => d.context === this.doc && this.childDocs.includes(d))) {
dragData.dropAction = targetAction;
- } else dragData.dropAction = this.props.Document[Id] === dragData?.treeViewId ? "same" : dragData.dropAction;
+ } else dragData.dropAction = this.doc === dragData?.treeViewDoc ? "same" : dragData.dropAction;
}
}
@@ -691,7 +694,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
@action
remove = (doc: Doc | Doc[]): boolean => {
const docs = doc instanceof Doc ? [doc] : doc;
- const targetDataDoc = this.props.Document[DataSym];
+ const targetDataDoc = this.doc[DataSym];
const value = DocListCast(targetDataDoc[this.props.fieldKey]);
const result = value.filter(v => !docs.includes(v));
if (result.length !== value.length) {
@@ -704,9 +707,9 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
addDoc = (doc: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
const doAddDoc = (doc: Doc | Doc[]) =>
(doc instanceof Doc ? [doc] : doc).reduce((flg, doc) =>
- flg && Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false), true);
- if (this.props.Document.resolvedDataDoc instanceof Promise) {
- this.props.Document.resolvedDataDoc.then((resolved: any) => doAddDoc(doc));
+ flg && Doc.AddDocToList(this.doc[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false), true);
+ if (this.doc.resolvedDataDoc instanceof Promise) {
+ this.doc.resolvedDataDoc.then((resolved: any) => doAddDoc(doc));
} else {
doAddDoc(doc);
}
@@ -714,25 +717,26 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
}
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
- if (!e.isPropagationStopped() && this.props.Document === Doc.UserDoc().myWorkspaces) {
+ if (!e.isPropagationStopped() && this.doc === Doc.UserDoc().myWorkspaces) {
ContextMenu.Instance.addItem({ description: "Create Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" });
- ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.remove(this.props.Document), icon: "minus" });
+ ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.remove(this.doc), icon: "minus" });
e.stopPropagation();
e.preventDefault();
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
- } else if (!e.isPropagationStopped() && this.props.Document === Doc.UserDoc().myRecentlyClosed) {
+ } else if (!e.isPropagationStopped() && this.doc === Doc.UserDoc().myRecentlyClosed) {
ContextMenu.Instance.addItem({ description: "Clear All", event: () => Doc.UserDoc().myRecentlyClosed = new List<Doc>(), icon: "plus" });
e.stopPropagation();
e.preventDefault();
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
} else {
const layoutItems: ContextMenuProps[] = [];
- layoutItems.push({ description: (this.props.Document.treeViewPreventOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.treeViewPreventOpen = !this.props.Document.treeViewPreventOpen, icon: "paint-brush" });
- layoutItems.push({ description: (this.props.Document.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.treeViewHideHeaderFields = !this.props.Document.treeViewHideHeaderFields, icon: "paint-brush" });
- layoutItems.push({ description: (this.props.Document.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.props.Document.treeViewHideTitle = !this.props.Document.treeViewHideTitle, icon: "paint-brush" });
+ layoutItems.push({ description: (this.doc.treeViewPreventOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.doc.treeViewPreventOpen = !this.doc.treeViewPreventOpen, icon: "paint-brush" });
+ layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields, icon: "paint-brush" });
+ layoutItems.push({ description: (this.doc.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.doc.treeViewHideTitle = !this.doc.treeViewHideTitle, icon: "paint-brush" });
+ layoutItems.push({ description: (this.doc.treeViewHideLinkLines ? "Show" : "Hide") + " Link Lines", event: () => this.doc.treeViewHideLinkLines = !this.doc.treeViewHideLinkLines, icon: "paint-brush" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
}
- ContextMenu.Instance.addItem({
+ !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({
description: "Buxton Layout", icon: "eye", event: () => {
const { ImageDocument, PdfDocument } = Docs.Create;
const { Document } = this.props;
@@ -745,7 +749,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
const fallback = ImageDocument("http://cs.brown.edu/~bcz/face.gif", { _width: 400 }); // replace with desired double click target
let pdfContent: string;
- DocListCast(this.dataDoc[this.props.fieldKey]).map(d => {
+ this.childDocs?.map(d => {
DocListCast(d.data).map((img, i) => {
const caption = (d.captions as any)[i];
if (caption) {
@@ -774,7 +778,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
onClicks.push({
- description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
+ description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
});
!existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
@@ -784,7 +788,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
@computed get renderClearButton() {
return <div id="toolbar" key="toolbar">
<button className="toolbar-button round-button" title="Empty"
- onClick={undoBatch(action(() => Doc.GetProto(this.props.Document)[this.props.fieldKey] = undefined))}>
+ onClick={undoBatch(action(() => Doc.GetProto(this.doc)[this.props.fieldKey] = undefined))}>
<FontAwesomeIcon icon={"trash"} size="sm" />
</button>
</div >;
@@ -794,51 +798,52 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
console.log(e);
}
render() {
- if (!(this.props.Document instanceof Doc)) return (null);
- const dropAction = StrCast(this.props.Document.childDropAction) as dropActionType;
+ TraceMobx();
+ if (!(this.doc instanceof Doc)) return (null);
+ const dropAction = StrCast(this.doc.childDropAction) as dropActionType;
const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(d, target, addDoc);
const childDocs = this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs;
return !childDocs ? (null) : (
- <div className="collectionTreeView-dropTarget" id="body"
- style={{
- background: this.props.backgroundColor?.(this.props.Document),
- paddingLeft: `${NumCast(this.props.Document._xPadding, 10)}px`,
- paddingRight: `${NumCast(this.props.Document._xPadding, 10)}px`,
- paddingTop: `${NumCast(this.props.Document._yPadding, 20)}px`,
- pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined
- }}
- onKeyPress={this.onKeyPress}
- onContextMenu={this.onContextMenu}
- onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
- onDrop={this.onTreeDrop}
- ref={this.createTreeDropTarget}>
- {(this.props.treeViewHideTitle || this.props.Document.treeViewHideTitle ? (null) : <EditableView
- contents={this.dataDoc.title}
- editing={false}
- display={"block"}
- maxHeight={72}
- height={"auto"}
- GetValue={() => StrCast(this.dataDoc.title)}
- SetValue={undoBatch((value: string) => Doc.SetInPlace(this.dataDoc, "title", value, false) || true)}
- OnFillDown={undoBatch((value: string) => {
- Doc.SetInPlace(this.dataDoc, "title", value, false);
- const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, _LODdisable: true, templates: new List<string>([Templates.Title.Layout]) });
- EditableView.loadId = doc[Id];
- Doc.SetInPlace(doc, "editTitle", true, false);
- this.addDoc(doc, childDocs.length ? childDocs[0] : undefined, true);
- })} />)}
- {this.props.Document.allowClear ? this.renderClearButton : (null)}
- <ul className="no-indent" style={{ width: "max-content" }} >
- {
- TreeView.GetChildElements(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.backgroundColor, this.props.ScreenToLocalTransform,
- this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.props.Document.treeViewHideHeaderFields),
- BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick,
- this.props.onChildClick || ScriptCast(this.props.Document.onChildClick), this.props.ignoreFields)
- }
- </ul>
- </div >
+ <div className="collectionTreeView-container" onContextMenu={this.onContextMenu}>
+ <div className="collectionTreeView-dropTarget" id="body"
+ style={{
+ background: this.props.backgroundColor?.(this.doc),
+ paddingLeft: `${NumCast(this.doc._xPadding, 10)}px`,
+ paddingRight: `${NumCast(this.doc._xPadding, 10)}px`,
+ paddingTop: `${NumCast(this.doc._yPadding, 20)}px`,
+ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined
+ }}
+ onKeyPress={this.onKeyPress}
+ onWheel={(e) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
+ onDrop={this.onTreeDrop}
+ ref={this.createTreeDropTarget}>
+ {this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? (null) : <EditableView
+ contents={this.dataDoc.title}
+ editing={false}
+ display={"block"}
+ maxHeight={72}
+ height={"auto"}
+ GetValue={() => StrCast(this.dataDoc.title)}
+ SetValue={undoBatch((value: string) => Doc.SetInPlace(this.dataDoc, "title", value, false) || true)}
+ OnFillDown={undoBatch((value: string) => {
+ Doc.SetInPlace(this.dataDoc, "title", value, false);
+ const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, _LODdisable: true, templates: new List<string>([Templates.Title.Layout]) });
+ Doc.SetInPlace(doc, "editTitle", "*", false);
+ this.addDoc(doc, childDocs.length ? childDocs[0] : undefined, true);
+ })} />}
+ {this.doc.allowClear ? this.renderClearButton : (null)}
+ <ul className="no-indent" style={{ width: "max-content" }} >
+ {
+ TreeView.GetChildElements(childDocs, this.doc, this.doc, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
+ moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
+ this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields),
+ BoolCast(this.doc.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick,
+ this.props.onChildClick || ScriptCast(this.doc.onChildClick), this.props.ignoreFields)
+ }
+ </ul>
+ </div >
+ </div>
);
}
}
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index 7877fe155..b630f9cf8 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -11,16 +11,18 @@
height: 100%;
overflow: hidden; // bcz: used to be 'auto' which would create scrollbars when there's a floating doc that's not visible. not sure if that's better, but the scrollbars are annoying...
- .collectionTimeView-dragger {
- background-color: lightgray;
+ .collectionView-filterDragger {
+ background-color: rgb(140, 139, 139);
height: 40px;
- width: 20px;
+ width: 10px;
position: absolute;
- border-radius: 10px;
top: 55%;
border: 1px black solid;
+ border-radius: 0;
+ border-top-left-radius: 20px;
+ border-bottom-left-radius: 20px;
+ border-right: unset;
z-index: 2;
- right: -10px;
}
.collectionTimeView-treeView {
display: flex;
@@ -30,6 +32,8 @@
position: absolute;
right: 0;
top: 0;
+ border-left: solid 1px;
+ z-index: 1;
.collectionTimeView-addfacet {
display: inline-block;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 78b70b5fa..5c87bc483 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -108,7 +108,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
get collectionViewType(): CollectionViewType | undefined {
const viewField = StrCast(this.props.Document._viewType);
if (CollectionView._safeMode) {
- if (viewField === CollectionViewType.Freeform) {
+ if (viewField === CollectionViewType.Freeform || viewField === CollectionViewType.Schema) {
return CollectionViewType.Tree;
}
if (viewField === CollectionViewType.Invalid) {
@@ -193,7 +193,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
// return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />;
}
- screenToLocalTransform = () => this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth());
+ screenToLocalTransform = () => this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth());
private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
const props: SubCollectionViewProps = { ...this.props, ...renderProps, ScreenToLocalTransform: this.screenToLocalTransform, CollectionView: this, annotationsKey: "" };
switch (type) {
@@ -436,7 +436,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
this._facetWidth = this.props.PanelWidth() - Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
return false;
- }), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0));
+ }), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0), false);
}
filterBackground = () => "rgba(105, 105, 105, 0.432)";
get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves)
@@ -535,7 +535,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
:
""))}
{!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) :
- <div className="collectionTimeView-dragger" title="library View Dragger" onPointerDown={this.onPointerDown} style={{ right: this.facetWidth() - 10 }} />
+ <div className="collectionView-filterDragger" title="library View Dragger" onPointerDown={this.onPointerDown}
+ style={{ right: this.facetWidth() - 1, top: this.props.Document._viewType === CollectionViewType.Docking ? "25%" : "55%" }} />
}
{this.facetWidth() < 10 ? (null) : this.filterView}
</div>);
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 31a086d93..276bccede 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -40,28 +40,41 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
_templateCommand = {
params: ["target", "source"], title: "=> item view",
script: "this.target.childLayout = getDocTemplate(this.source?.[0])",
- immediate: (source: Doc[]) => this.target.childLayout = Doc.getDocTemplate(source?.[0]),
+ immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayout = Doc.getDocTemplate(source?.[0]))),
initialize: emptyFunction,
};
_narrativeCommand = {
params: ["target", "source"], title: "=> child click view",
script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
- immediate: (source: Doc[]) => this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]),
+ immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
initialize: emptyFunction,
};
_contentCommand = {
- params: ["target", "source"], title: "=> content",
+ params: ["target", "source"], title: "=> clear content",
script: "getProto(this.target).data = copyField(this.source);",
- immediate: (source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source), // Doc.aliasDocs(source),
+ immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)), // Doc.aliasDocs(source),
initialize: emptyFunction,
};
_viewCommand = {
- params: ["target"], title: "=> saved view",
+ params: ["target"], title: "=> reset view",
script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;",
- immediate: (source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target.scale = 1; },
+ immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target.scale = 1; }),
initialize: (button: Doc) => { button.restoredPanX = this.target._panX; button.restoredPanY = this.target._panY; button.restoredScale = this.target.scale; },
};
- _freeform_commands = [this._contentCommand, this._templateCommand, this._narrativeCommand, this._viewCommand];
+ _clusterCommand = {
+ params: ["target"], title: "=> fit content",
+ script: "this.target._fitToBox = !this.target._fitToBox;",
+ immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox),
+ initialize: emptyFunction
+ };
+ _fitContentCommand = {
+ params: ["target"], title: "=> toggle clusters",
+ script: "this.target.useClusters = !this.target.useClusters;",
+ immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters),
+ initialize: emptyFunction
+ };
+
+ _freeform_commands = [this._viewCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
_stacking_commands = [this._contentCommand, this._templateCommand];
_masonry_commands = [this._contentCommand, this._templateCommand];
_schema_commands = [this._templateCommand, this._narrativeCommand];
@@ -85,6 +98,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@observable private _currentKey: string = "";
componentDidMount = action(() => {
+ this._currentKey = this._currentKey || (this._buttonizableCommands.length ? this._buttonizableCommands[0]?.title : "");
// chrome status is one of disabled, collapsed, or visible. this determines initial state from document
switch (this.props.CollectionView.props.Document._chromeStatus) {
case "disabled":
@@ -250,7 +264,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
{ target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
return true;
- }, emptyFunction, emptyFunction);
+ }, emptyFunction, () => {
+ this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate([]));
+ });
}
@computed get templateChrome() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 6cac39f77..a24693c30 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -56,27 +56,27 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const targetAhyperlink = linkEles.find((ele: any) => ele.getAttribute("targetids")?.includes(AanchorId));
const targetBhyperlink = linkEles.find((ele: any) => ele.getAttribute("targetids")?.includes(BanchorId));
if (!targetBhyperlink) {
- this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
- this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
+ this.props.A.rootDoc[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
+ this.props.A.rootDoc[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
} else {
setTimeout(() => {
- (this.props.A.props.Document[(this.props.A.props as any).fieldKey] as Doc);
+ (this.props.A.rootDoc[(this.props.A.props as any).fieldKey] as Doc);
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;
+ this.props.A.rootDoc[afield + "_x"] = Math.min(1, mp[0] / this.props.A.props.PanelWidth()) * 100;
+ this.props.A.rootDoc[afield + "_y"] = Math.min(1, mp[1] / this.props.A.props.PanelHeight()) * 100;
}, 0);
}
if (!targetAhyperlink) {
- this.props.A.props.Document[bfield + "_x"] = (bpt.point.x - bbounds.left) / bbounds.width * 100;
- this.props.A.props.Document[bfield + "_y"] = (bpt.point.y - bbounds.top) / bbounds.height * 100;
+ this.props.A.rootDoc[bfield + "_x"] = (bpt.point.x - bbounds.left) / bbounds.width * 100;
+ this.props.A.rootDoc[bfield + "_y"] = (bpt.point.y - bbounds.top) / bbounds.height * 100;
} else {
setTimeout(() => {
- (this.props.B.props.Document[(this.props.B.props as any).fieldKey] as Doc);
+ (this.props.B.rootDoc[(this.props.B.props as any).fieldKey] as Doc);
const m = targetAhyperlink.getBoundingClientRect();
const mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
- this.props.B.props.Document[bfield + "_x"] = mp[0] / this.props.B.props.PanelWidth() * 100;
- this.props.B.props.Document[bfield + "_y"] = mp[1] / this.props.B.props.PanelHeight() * 100;
+ this.props.B.rootDoc[bfield + "_x"] = Math.min(1, mp[0] / this.props.B.props.PanelWidth()) * 100;
+ this.props.B.rootDoc[bfield + "_y"] = Math.min(1, mp[1] / this.props.B.props.PanelHeight()) * 100;
}, 0);
}
})
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index ae81b4b36..1a2421bfd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -10,6 +10,7 @@ import React = require("react");
import { Utils, emptyFunction } from "../../../../Utils";
import { DocumentType } from "../../../documents/DocumentTypes";
import { SnappingManager } from "../../../util/SnappingManager";
+import { Cast } from "../../../../fields/Types";
@observer
export class CollectionFreeFormLinksView extends React.Component {
@@ -30,8 +31,8 @@ export class CollectionFreeFormLinksView extends React.Component {
return drawnPairs;
}, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]);
return connections.filter(c =>
- c.a.props.Document.type === DocumentType.LINK &&
- c.a.props.pinToPres !== emptyFunction && c.b.props.pinToPres !== emptyFunction // bcz: this prevents links to be drawn to anchors in CollectionTree views -- this is a hack that should be fixed
+ c.a.props.Document.type === DocumentType.LINK
+ && !c.a.props.treeViewDoc?.treeViewHideLinkLines && !c.b.props.treeViewDoc?.treeViewHideLinkLines
).map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index d9011c9d3..92aee3776 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,7 +1,6 @@
@import "../../globalCssVariables";
-.collectionfreeformview-none,
-.collectionfreeformview-ease {
+.collectionfreeformview-none {
position: inherit;
top: 0;
left: 0;
@@ -22,10 +21,6 @@
}
}
-.collectionfreeformview-ease {
- transition: transform 500ms;
-}
-
.collectionfreeformview-none {
touch-action: none;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 00260745d..5135c4ae4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -45,7 +45,9 @@ import React = require("react");
import { CollectionViewType } from "../CollectionView";
import { Timeline } from "../../animationtimeline/Timeline";
import { SnappingManager } from "../../../util/SnappingManager";
-import { InkingStroke, ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox } from "../../InkingStroke";
+import { InkingStroke, ActiveArrowStart, ActiveArrowEnd, ActiveInkColor, ActiveFillColor, ActiveInkWidth, ActiveInkBezierApprox, ActiveDash } from "../../InkingStroke";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -61,7 +63,7 @@ export const panZoomSchema = createSchema({
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",
+ _viewTransition: "string",
scrollHeight: "number",
fitX: "number",
fitY: "number",
@@ -108,9 +110,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@computed get nativeWidth() { return this.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
@computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
- private get scaleFieldKey() { return this.props.scaleField || "scale"; }
+ private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
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 zoomScaling = () => (this.fitToContentScaling / this.parentScaling) * (this.fitToContent ?
@@ -239,12 +240,19 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@undoBatch
@action
internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
- if (linkDragData.linkSourceDocument === this.props.Document) return false;
- const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
- this.props.addDocument(source);
- linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
- e.stopPropagation();
- return true;
+ if (linkDragData.linkSourceDocument === this.props.Document || this.props.Document.annotationOn) return false;
+ if (!linkDragData.linkSourceDocument.context || StrCast(Cast(linkDragData.linkSourceDocument.context, Doc, null)?.type) === DocumentType.COL) {
+ // const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
+ // this.props.addDocument(source);
+ // linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
+ return false;
+ } else {
+ const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
+ this.props.addDocument(source);
+ linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
+ e.stopPropagation();
+ return true;
+ }
}
@action
@@ -454,7 +462,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
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(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), points,
+ const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
{ title: "ink stroke", x: B.x - Number(ActiveInkWidth()) / 2, y: B.y - Number(ActiveInkWidth()) / 2, _width: B.width + Number(ActiveInkWidth()), _height: B.height + Number(ActiveInkWidth()) });
this.addDocument(inkDoc);
e.stopPropagation();
@@ -581,6 +589,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onClick = (e: React.MouseEvent) => {
if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
if (Date.now() - this._lastTap < 300) {
+ runInAction(() => DocumentLinksButton.StartLink = undefined);
const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
this.scaleAtPt(docpt, 1);
e.stopPropagation();
@@ -822,7 +831,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
}
if (!this.layoutDoc._lockedTransform || this.Document.inOverlay) {
- this.Document.panTransformType = panType;
+ this.Document._viewTransition = panType;
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));
@@ -850,7 +859,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
scaleAtPt(docpt: number[], scale: number) {
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
- this.Document.panTransformType = "Ease";
+ this.Document._viewTransition = "transform 500ms";
this.layoutDoc[this.scaleFieldKey] = scale;
const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
@@ -895,14 +904,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
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[this.scaleFieldKey], pt: this.Document.panTransformType };
+ const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition };
// if (!willZoom && DocumentView._focusHack.length) {
// Doc.BrushDoc(this.props.Document);
// !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
// } else {
if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
- if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+ if (!doc.z) this.setPan(newPanX, newPanY, "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
}
Doc.BrushDoc(this.props.Document);
this.props.focus(this.props.Document);
@@ -915,7 +924,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.Document._panX = savedState.px;
this.Document._panY = savedState.py;
this.Document[this.scaleFieldKey] = savedState.s;
- this.Document.panTransformType = savedState.pt;
+ this.Document._viewTransition = savedState.pt;
}
}, 500);
}
@@ -1001,7 +1010,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const { z, color, zIndex } = params.pair.layout;
return {
x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
- transition: StrCast(layoutDoc.transition), opacity: this.Document.editing ? 1 : Cast(opacity, "number", null),
+ transition: StrCast(layoutDoc.dataTransition), opacity: this.Document.editing ? 1 : Cast(opacity, "number", null),
width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
};
}
@@ -1128,6 +1137,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.props.Document[this.scaleFieldKey] = Math.max(1, NumCast(this.props.Document[this.scaleFieldKey]));
}
+ this.Document.useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
return elements;
}
@@ -1197,57 +1207,58 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onContextMenu = (e: React.MouseEvent) => {
if (this.props.annotationsKey) return;
- ContextMenu.Instance.addItem({
- description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
- this._timelineVisible = !this._timelineVisible;
- }), icon: this._timelineVisible ? faEyeSlash : faEye
- });
+ const appearance = ContextMenu.Instance.findByDescription("Appearance...");
+ const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
+ appearanceItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
+ appearanceItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+ appearanceItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+ appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
+ !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
const options = ContextMenu.Instance.findByDescription("Options...");
- const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
-
- optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
- optionItems.push({ description: "toggle snap line display", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
- optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
- optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
- optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
- optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
- this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
+ const optionItems = options && "subitems" in options ? options.subitems : [];
+ !this.props.isAnnotationOverlay &&
+ optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: faEye });
+ this.props.ContainingCollectionView &&
+ optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
+ optionItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " snap lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
- // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
- optionItems.push({
- description: "Import document", icon: "upload", event: ({ x, y }) => {
- const input = document.createElement("input");
- input.type = "file";
- input.accept = ".zip";
- input.onchange = async _e => {
- const upload = Utils.prepend("/uploadDoc");
- const formData = new FormData();
- const file = input.files && input.files[0];
- if (file) {
- formData.append('file', file);
- formData.append('remap', "true");
- const response = await fetch(upload, { method: "POST", body: formData });
- const json = await response.json();
- if (json !== "error") {
- const doc = await DocServer.GetRefField(json);
- if (doc instanceof Doc) {
- const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
- doc.x = xx, doc.y = yy;
- this.props.addDocument?.(doc);
+ if (!Doc.UserDoc().noviceMode) {
+ optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
+ optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+ optionItems.push({
+ description: "Import document", icon: "upload", event: ({ x, y }) => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = ".zip";
+ input.onchange = async _e => {
+ const upload = Utils.prepend("/uploadDoc");
+ const formData = new FormData();
+ const file = input.files && input.files[0];
+ if (file) {
+ formData.append('file', file);
+ formData.append('remap', "true");
+ const response = await fetch(upload, { method: "POST", body: formData });
+ const json = await response.json();
+ if (json !== "error") {
+ const doc = await DocServer.GetRefField(json);
+ if (doc instanceof Doc) {
+ const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
+ doc.x = xx, doc.y = yy;
+ this.props.addDocument?.(doc);
+ }
}
}
- }
- };
- input.click();
- }
- });
- optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
- ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+ };
+ input.click();
+ }
+ });
+ }
+ !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
}
- @observable _timelineVisible = false;
+ @observable showTimeline = false;
intersectRect(r1: { left: number, top: number, width: number, height: number },
r2: { left: number, top: number, width: number, height: number }) {
@@ -1321,9 +1332,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
nudge = action((x: number, y: number) => {
if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform) { // bcz: this isn't ideal, but want to try it out...
this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
- NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "Ease", true);
+ NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "transform 500ms", true);
this._nudgeTime = Date.now();
- setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document.panTransformType = undefined), 500);
+ setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document._viewTransition = undefined), 500);
return true;
}
return false;
@@ -1342,13 +1353,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
<CollectionFreeFormViewPannableContents
centeringShiftX={this.centeringShiftX}
centeringShiftY={this.centeringShiftY}
- easing={this.easing}
- transition={Cast(this.layoutDoc.transition, "string", null)}
+ transition={Cast(this.layoutDoc._viewTransition, "string", null)}
viewDefDivClick={this.props.viewDefDivClick}
zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
{this.children}
</CollectionFreeFormViewPannableContents>
- {this._timelineVisible ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
+ {this.showTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
</MarqueeView>;
}
@@ -1371,9 +1381,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onPointerDown={this.onPointerDown}
onPointerMove={this.onCursorMove}
onDrop={this.onExternalDrop.bind(this)}
- onDragOver={e => {
- e.preventDefault();
- }}
+ onDragOver={e => e.preventDefault()}
onContextMenu={this.onContextMenu}
style={{
pointerEvents: this.backgroundEvents ? "all" : undefined,
@@ -1424,7 +1432,6 @@ interface CollectionFreeFormViewPannableContentsProps {
panX: () => number;
panY: () => number;
zoomScaling: () => number;
- easing: () => boolean;
viewDefDivClick?: ScriptField;
children: () => JSX.Element[];
transition?: string;
@@ -1433,7 +1440,7 @@ interface CollectionFreeFormViewPannableContentsProps {
@observer
class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
render() {
- const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : (this.props.easing() ? "-ease" : "-none"));
+ const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : "-none");
const cenx = this.props.centeringShiftX();
const ceny = this.props.centeringShiftY();
const panx = -this.props.panX();
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
index a7f4d4e53..a9fab4c1e 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
@@ -1,5 +1,10 @@
.antimodeMenu-button {
- .color-preview {
+ .color-previewI {
+ width: 100%;
+ height: 40%;
+ }
+
+ .color-previewII {
width: 100%;
height: 100%;
}
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index ae82c6a65..f1032adaa 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -3,25 +3,44 @@ import AntimodeMenu from "../../AntimodeMenu";
import { observer } from "mobx-react";
import { observable, action, computed } from "mobx";
import "./InkOptionsMenu.scss";
-import { ActiveInkColor, ActiveInkBezierApprox, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox } from "../../InkingStroke";
+import { ActiveInkColor, ActiveInkBezierApprox, ActiveFillColor, ActiveArrowStart, ActiveArrowEnd, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke";
import { Scripting } from "../../../util/Scripting";
import { InkTool } from "../../../../fields/InkField";
import { ColorState } from "react-color";
import { Utils } from "../../../../Utils";
import GestureOverlay from "../../GestureOverlay";
import { Doc } from "../../../../fields/Doc";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { DocumentView } from "../../../views/nodes/DocumentView";
+import { Document } from "../../../../fields/documentSchemas";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faSleigh, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve, } from "@fortawesome/free-solid-svg-icons";
+
+library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve);
@observer
export default class InkOptionsMenu extends AntimodeMenu {
static Instance: InkOptionsMenu;
- private _palette = ["D0021B", "F5A623", "F8E71C", "8B572A", "7ED321", "417505", "9013FE", "4A90E2", "50E3C2", "B8E986", "000000", "4A4A4A", "9B9B9B", "FFFFFF"];
- private _width = ["1", "5", "10", "100", "200", "300"];
- private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"];
- private _icons = ["O", "∆", "ロ", "➜", "-"];
+ private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", "none"];
+ private _width = ["1", "5", "10", "100"];
+ // private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"];
+ // private _icons = ["O", "∆", "ロ", "➜", "-"];
+ private _buttons = ["circle", "triangle", "rectangle", "line", "noRec", "",];
+ private _icons = ["O", "∆", "ロ", "⎯", "✖︎", " "];
+ //arrowStart and arrowEnd must match and defs must exist in Inking Stroke
+ private _arrowStart = ["arrowHead", "arrowHead", "dot", "dot", "none"];
+ private _arrowEnd = ["none", "arrowEnd", "none", "dot", "none"];
+ private _arrowIcons = ["→", "↔︎", "•", "••", " "];
@observable _colorBtn = false;
@observable _widthBtn = false;
+ @observable _fillBtn = false;
+ @observable _arrowBtn = false;
+ @observable _dashBtn = false;
+ @observable _shapeBtn = false;
constructor(props: Readonly<{}>) {
super(props);
@@ -29,18 +48,106 @@ export default class InkOptionsMenu extends AntimodeMenu {
this._canFade = false; // don't let the inking menu fade away
}
+ getColors = () => {
+ return this._palette;
+ }
+
@action
- changeColor = (color: string) => {
+ changeArrow = (arrowStart: string, arrowEnd: string) => {
+ SetActiveArrowStart(arrowStart);
+ SetActiveArrowEnd(arrowEnd);
+ }
+
+ @action
+ changeColor = (color: string, type: string) => {
const col: ColorState = {
hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
};
- SetActiveInkColor(Utils.colorString(col));
+ if (type === "color") {
+ SetActiveInkColor(Utils.colorString(col));
+ } else if (type === "fill") {
+ SetActiveFillColor(Utils.colorString(col));
+ }
}
@action
+ editProperties = (value: any, field: string) => {
+ SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+ const doc = Document(element.rootDoc);
+ if (doc.type === DocumentType.INK) {
+ switch (field) {
+ case "width":
+ doc.strokeWidth = Number(value);
+ break;
+ case "color":
+ doc.color = String(value);
+ break;
+ case "fill":
+ doc.fillColor = String(value);
+ break;
+ case "bezier":
+ // doc.strokeBezier === 300 ? doc.strokeBezier = 0 : doc.strokeBezier = 300;
+ break;
+ case "arrowStart":
+ doc.arrowStart = String(value);
+ break;
+ case "arrowEnd":
+ doc.arrowEnd = String(value);
+ break;
+ case "dash":
+ doc.dash = Number(value);
+ default:
+ break;
+ }
+ }
+ }));
+ }
+
+
+ @action
changeBezier = (e: React.PointerEvent): void => {
SetActiveBezierApprox(!ActiveInkBezierApprox() ? "300" : "");
+ this.editProperties(0, "bezier");
+ }
+ @action
+ changeDash = (e: React.PointerEvent): void => {
+ SetActiveDash(ActiveDash() === "0" ? "2" : "0");
+ this.editProperties(ActiveDash(), "dash");
+ }
+
+ @computed get arrowPicker() {
+ var currIcon;
+ for (var i = 0; i < this._arrowStart.length; i++) {
+ if (this._arrowStart[i] === ActiveArrowStart() && this._arrowEnd[i] === ActiveArrowEnd()) {
+ currIcon = this._arrowIcons[i];
+ if (this._arrowIcons[i] === " ") {
+ currIcon = "➤";
+ }
+ }
+ }
+ var arrowPicker = <button
+ className="antimodeMenu-button"
+ key="arrow"
+ onPointerDown={action(e => this._arrowBtn = !this._arrowBtn)}
+ style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
+ {currIcon}
+ </button>;
+ if (this._arrowBtn) {
+ arrowPicker = <div className="btn2-group" key="arrows">
+ {arrowPicker}
+ {this._arrowStart.map((arrowStart, i) => {
+ return <button
+ className="antimodeMenu-button"
+ key={arrowStart}
+ onPointerDown={action(() => { SetActiveArrowStart(arrowStart); SetActiveArrowEnd(this._arrowEnd[i]); this.editProperties(arrowStart, "arrowStart"), this.editProperties(this._arrowEnd[i], "arrowEnd"); this._arrowBtn = false; })}
+ style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
+ {this._arrowIcons[i]}
+ </button>;
+ })}
+ </div>;
+ }
+ return arrowPicker;
}
@computed get widthPicker() {
@@ -49,7 +156,7 @@ export default class InkOptionsMenu extends AntimodeMenu {
key="width"
onPointerDown={action(e => this._widthBtn = !this._widthBtn)}
style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
- W
+ <FontAwesomeIcon icon="bars" size="lg" />
</button>;
if (this._widthBtn) {
widthPicker = <div className="btn2-group" key="width">
@@ -58,7 +165,7 @@ export default class InkOptionsMenu extends AntimodeMenu {
return <button
className="antimodeMenu-button"
key={wid}
- onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; })}
+ onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
{wid}
</button>;
@@ -68,6 +175,8 @@ export default class InkOptionsMenu extends AntimodeMenu {
return widthPicker;
}
+
+
@computed get colorPicker() {
var colorPicker = <button
className="antimodeMenu-button"
@@ -75,7 +184,9 @@ export default class InkOptionsMenu extends AntimodeMenu {
title="colorChanger"
onPointerDown={action(e => this._colorBtn = !this._colorBtn)}
style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
- <div className="color-preview" style={{ backgroundColor: ActiveInkColor() ?? "121212" }}></div>
+ <FontAwesomeIcon icon="pen-nib" size="lg" />
+ <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }}></div>
+
</button>;
if (this._colorBtn) {
colorPicker = <div className="btn-group" key="color">
@@ -84,9 +195,10 @@ export default class InkOptionsMenu extends AntimodeMenu {
return <button
className="antimodeMenu-button"
key={color}
- onPointerDown={action(() => { this.changeColor(color); this._colorBtn = false; })}
+ onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
- <div className="color-preview" style={{ backgroundColor: color }}></div>
+ {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
+ <div className="color-previewII" style={{ backgroundColor: color }}></div>
</button>;
})}
</div>;
@@ -94,15 +206,75 @@ export default class InkOptionsMenu extends AntimodeMenu {
return colorPicker;
}
- @computed get shapeButtons() {
- return this._buttons.map((btn, i) => <button
+ @computed get fillPicker() {
+ var fillPicker = <button
className="antimodeMenu-button"
- title={`Draw ${btn}`}
- key={i}
- onPointerDown={action(e => GestureOverlay.Instance.InkShape = btn)}
- style={{ backgroundColor: btn === GestureOverlay.Instance.InkShape ? "121212" : "" }}>
- {this._icons[i]}
- </button>);
+ key="fill"
+ title="fillChanger"
+ onPointerDown={action(e => this._fillBtn = !this._fillBtn)}
+ style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
+ <FontAwesomeIcon icon="fill-drip" size="lg" />
+ <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }}></div>
+ </button>;
+ if (this._fillBtn) {
+ fillPicker = <div className="btn-group" key="fill">
+ {fillPicker}
+ {this._palette.map(color => {
+ return <button
+ className="antimodeMenu-button"
+ key={color}
+ onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
+ style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
+ <div className="color-previewII" style={{ backgroundColor: color }}></div>
+ </button>;
+ })}
+
+ </div>;
+ }
+ return fillPicker;
+ }
+
+ @computed get shapePicker() {
+ var currIcon;
+ if (GestureOverlay.Instance.InkShape === "") {
+ currIcon = <FontAwesomeIcon icon="shapes" size="lg" />;
+ } else {
+ for (var i = 0; i < this._icons.length; i++) {
+ if (GestureOverlay.Instance.InkShape === this._buttons[i]) {
+ currIcon = this._icons[i];
+ }
+ }
+ }
+ var shapePicker = <button
+ className="antimodeMenu-button"
+ key="shape"
+ onPointerDown={action(e => this._shapeBtn = !this._shapeBtn)}
+ style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
+ {currIcon}
+ </button>;
+ if (this._shapeBtn) {
+ shapePicker = <div className="btn2-group" key="shape">
+ {shapePicker}
+ {this._buttons.map((btn, i) => {
+ var ttl = btn;
+ if (btn === "") {
+ ttl = "no shape";
+ }
+ if (btn === "noRec") {
+ ttl = "disable shape recognition";
+ }
+ return <button
+ className="antimodeMenu-button"
+ title={`Draw ${btn}`}
+ key={ttl}
+ onPointerDown={action((e) => { GestureOverlay.Instance.InkShape = btn; this._shapeBtn = false; })}
+ style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
+ {this._icons[i]}
+ </button>;
+ })}
+ </div>;
+ }
+ return shapePicker;
}
@computed get bezierButton() {
@@ -112,17 +284,35 @@ export default class InkOptionsMenu extends AntimodeMenu {
key="bezier"
onPointerDown={e => this.changeBezier(e)}
style={{ backgroundColor: ActiveInkBezierApprox() ? "121212" : "" }}>
- B
+ <FontAwesomeIcon icon="bezier-curve" size="lg" />
+
+ </button>;
+ }
+
+ @computed get dashButton() {
+ return <button
+ className="antimodeMenu-button"
+ title="dash changer"
+ key="dash"
+ onPointerDown={e => this.changeDash(e)}
+ style={{ backgroundColor: ActiveDash() !== "0" ? "121212" : "" }}>
+ <FontAwesomeIcon icon="ellipsis-h" size="lg" />
+
</button>;
}
render() {
const buttons = [
- <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}> ✜ </button>,
- ...this.shapeButtons,
+ // <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}>
+ // <FontAwesomeIcon icon="arrows-alt" size="lg" />
+ // </button>,
+ this.shapePicker,
this.bezierButton,
this.widthPicker,
this.colorPicker,
+ this.fillPicker,
+ this.arrowPicker,
+ this.dashButton,
];
return this.getElement(buttons);
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5f09fa0ee..099859109 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,6 +1,6 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt } from "../../../../fields/Doc";
+import { Doc, Opt, DocListCast, DataSym } from "../../../../fields/Doc";
import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { RichTextField } from "../../../../fields/RichTextField";
@@ -25,7 +25,7 @@ interface MarqueeViewProps {
getContainerTransform: () => Transform;
getTransform: () => Transform;
activeDocuments: () => Doc[];
- selectDocuments: (docs: Doc[], ink: { Document: Doc, Ink: Map<any, any> }[]) => void;
+ selectDocuments: (docs: Doc[]) => void;
addLiveTextDocument: (doc: Doc) => void;
isSelected: () => boolean;
nudge: (x: number, y: number) => boolean;
@@ -80,11 +80,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
});
ContextMenu.Instance.displayMenu(this._downX, this._downY);
+ e.stopPropagation();
} else
if (e.key === ":") {
DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument, x, y);
ContextMenu.Instance.displayMenu(this._downX, this._downY);
+ e.stopPropagation();
+ } else if (e.key === "a" && (e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ this.props.selectDocuments(this.props.activeDocuments());
+ e.stopPropagation();
} else if (e.key === "q" && e.ctrlKey) {
e.preventDefault();
(async () => {
@@ -108,6 +114,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
y += 40 * this.props.getTransform().Scale;
});
})();
+ e.stopPropagation();
} else if (e.key === "b" && e.ctrlKey) {
e.preventDefault();
navigator.clipboard.readText().then(text => {
@@ -118,7 +125,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.pasteTable(ns, x, y);
}
});
- } else if (!e.ctrlKey) {
+ e.stopPropagation();
+ } else if (!e.ctrlKey && !e.metaKey) {
FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
const tbox = Docs.Create.TextDocument("", {
_width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
@@ -132,8 +140,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
}
this.props.addLiveTextDocument(tbox);
+ e.stopPropagation();
}
- e.stopPropagation();
}
//heuristically converts pasted text into a table.
// assumes each entry is separated by a tab
@@ -225,7 +233,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
// let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map();
// let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : [];
const docs = mselect.length ? mselect : [this.props.Document];
- this.props.selectDocuments(docs, []);
+ this.props.selectDocuments(docs);
}
const hideMarquee = () => {
this.hideMarquee();
@@ -250,6 +258,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
e.preventDefault();
}
}
+ clearSelection() {
+ if (window.getSelection) { window.getSelection()?.removeAllRanges(); }
+ else if (document.getSelection()) { document.getSelection()?.empty(); }
+ }
setPreviewCursor = action((x: number, y: number, drag: boolean) => {
if (drag) {
@@ -265,6 +277,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._downX = x;
this._downY = y;
PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
+ this.clearSelection();
}
});
@@ -354,7 +367,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
selected.forEach(d => this.props.removeDocument(d));
const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
this.props.addDocument(newCollection!);
- this.props.selectDocuments([newCollection!], []);
+ this.props.selectDocuments([newCollection!]);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
}
@@ -379,7 +392,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined);
this.props.addDocument(newCollection);
- this.props.selectDocuments([newCollection], []);
+ this.props.selectDocuments([newCollection]);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
}
@@ -492,7 +505,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.props.addDocument(newCollection);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- setTimeout(() => this.props.selectDocuments([newCollection], []), 0);
+ setTimeout(() => this.props.selectDocuments([newCollection]), 0);
}
@undoBatch