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/CollectionLinearView.tsx4
-rw-r--r--src/client/views/collections/CollectionMenu.tsx45
-rw-r--r--src/client/views/collections/CollectionSchemaHeaders.tsx14
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss7
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx148
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx12
-rw-r--r--src/client/views/collections/CollectionSubView.tsx5
-rw-r--r--src/client/views/collections/CollectionView.tsx14
-rw-r--r--src/client/views/collections/SchemaTable.tsx14
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx173
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx56
11 files changed, 236 insertions, 256 deletions
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 859ee9362..0eac5136a 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -38,8 +38,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
componentDidMount() {
// is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported).
- this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.linearViewIsExpanded ? 1 : 0),
- () => this.props.Document._width = 5 + (this.props.Document.linearViewIsExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+ this._widthDisposer = reaction(() => 5 + (this.props.Document.linearViewIsExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+ width => this.childDocs.length && (this.props.Document._width = width),
{ fireImmediately: true }
);
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 09ff3bb0c..bf6067978 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -529,12 +529,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
}
@computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
@computed get isText() {
- if (this.selectedDoc) {
- const layoutField = Doc.LayoutField(this.selectedDoc);
- const layoutStr = this.selectedDocumentView?.props.LayoutTemplateString || StrCast(layoutField);
- return (document.activeElement as any)?.className.includes("ProseMirror") || layoutStr.includes("FormattedText") || StrCast((layoutField as Doc)?.layout).includes("FormattedText");
- }
- else return false;
+ return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any)?.focused ? true : false;
}
@undoBatch
@@ -843,22 +838,26 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
render() {
return !this.props.docView.layoutDoc ? (null) :
<div className="collectionFreeFormMenu-cont">
- {!Doc.UserDoc().noviceMode && !this.isText && !this.props.isDoc ? <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom">
- <div className="backKeyframe" onClick={this.prevKeyframe}>
- <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
- </div>
- </Tooltip> : null}
- {!Doc.UserDoc().noviceMode && !this.isText && !this.props.isDoc ? <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom">
- <div className="numKeyframe" style={{ color: this.document.editing ? "white" : "black", backgroundColor: this.document.editing ? "#5B9FDD" : "#AEDDF8" }}
- onClick={action(() => this.document.editing = !this.document.editing)} >
- {NumCast(this.document._currentFrame)}
- </div>
- </Tooltip> : null}
- {!Doc.UserDoc().noviceMode && !this.isText && !this.props.isDoc ? <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom">
- <div className="fwdKeyframe" onClick={this.nextKeyframe}>
- <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
- </div>
- </Tooltip> : null}
+ {!Doc.UserDoc().noviceMode && !this.isText && !this.props.isDoc ?
+ <>
+ <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom">
+ <div className="backKeyframe" onClick={this.prevKeyframe}>
+ <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+ </div>
+ </Tooltip>
+ <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom">
+ <div className="numKeyframe" style={{ color: this.document.editing ? "white" : "black", backgroundColor: this.document.editing ? "#5B9FDD" : "#AEDDF8" }}
+ onClick={action(() => this.document.editing = !this.document.editing)} >
+ {NumCast(this.document._currentFrame)}
+ </div>
+ </Tooltip>
+ <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom">
+ <div className="fwdKeyframe" onClick={this.nextKeyframe}>
+ <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+ </div>
+ </Tooltip>
+ </>
+ : null}
{!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText || this.props.isDoc ? (null) :
this.urlEditor
@@ -872,7 +871,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</> :
(null)
}
- {this.isText ? <RichTextMenu /> : null}
+ {<div style={{ display: !this.isText ? "none" : undefined }}><RichTextMenu /></div>}
</div>;
}
}
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index dbf7488ec..b408fd680 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -261,7 +261,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
componentDidMount() {
document.addEventListener("pointerdown", this.detectClick);
const filters = Cast(this.props.Document._docFilters, listSpec("string"));
- if (filters?.includes(this._key)) {
+ if (filters?.some(filter => filter.split(":")[0] === this._key)) {
runInAction(() => this.closeResultsVisibility = "contents");
}
}
@@ -396,19 +396,21 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
});
const filters = Cast(this.props.Document._docFilters, listSpec("string"));
- if (filters === undefined || filters.length === 0 || filters.includes(this._key) === false) {
+ if (filters === undefined || filters.length === 0 || filters.some(filter => filter.split(":")[0] === this._key) === false) {
this.props.col.setColor("rgb(241, 239, 235)");
this.closeResultsVisibility = "none";
}
- for (let i = 0; i < (filters?.length ?? 0) - 1; i += 3) {
- if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i + 1]) === false) {
+ for (let i = 0; i < (filters?.length ?? 0) - 1; i++) {
+ if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i].split(":")[1]) === false) {
keyOptions.push(filters![i + 1]);
}
}
const options = keyOptions.map(key => {
let bool = false;
if (filters !== undefined) {
- bool = filters.includes(key) && filters[filters.indexOf(key) + 1] === "check";
+ const ind = filters.findIndex(filter => filter.split(":")[0] === key);
+ const fields = ind === -1 ? undefined : filters[ind].split(":");
+ bool = fields ? fields[1] === "check" : false;
}
return <div key={key} className="key-option" style={{
border: "1px solid lightgray", paddingLeft: 5, textAlign: "left",
@@ -453,7 +455,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
updateFilter() {
const filters = Cast(this.props.Document._docFilters, listSpec("string"));
- if (filters === undefined || filters.length === 0 || filters.includes(this._key) === false) {
+ if (filters === undefined || filters.length === 0 || filters.some(filter => filter.split(":")[0] === this._key) === false) {
this.props.col.setColor("rgb(241, 239, 235)");
this.closeResultsVisibility = "none";
}
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 8d2f645d9..2bdd280ec 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -30,7 +30,7 @@
.collectionSchemaView-dividerDragger {
position: relative;
height: 100%;
- width: 20px;
+ width: $SCHEMA_DIVIDER_WIDTH;
z-index: 20;
right: 0;
top: 0;
@@ -228,6 +228,9 @@
position: absolute;
background: white;
padding: 5px;
+ position: fixed;
+ background: white;
+ border: black 1px solid;
.collectionSchema-header-toggler {
z-index: 100;
@@ -500,7 +503,7 @@ button.add-column {
border: grey;
border-style: solid;
border-width: 1px;
- height: 100%;
+ height: 30px;
.collectionSchemaView-dropdownButton {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 27575374a..9f8468253 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -11,12 +11,12 @@ import { listSpec } from "../../../fields/Schema";
import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { Cast, NumCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents } from "../../../Utils";
+import { emptyFunction, returnFalse, returnOne, setupMoveUpEvents } from "../../../Utils";
import { SelectionManager } from "../../util/SelectionManager";
import { SnappingManager } from "../../util/SnappingManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
-import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
+import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../views/globalCssVariables.scss';
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import '../DocumentDecorations.scss';
@@ -47,24 +47,26 @@ const columnTypes: Map<string, ColumnType> = new Map([
@observer
export class CollectionSchemaView extends CollectionSubView(doc => doc) {
private _previewCont?: HTMLDivElement;
- private DIVIDER_WIDTH = 4;
-
- @observable previewDoc: Doc | undefined = undefined;
- @observable private _focusedTable: Doc = this.props.Document;
-
- @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
- @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
- @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH - this.previewWidth(); }
- @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
+ @observable _previewDoc: Doc | undefined = undefined;
+ @observable _focusedTable: Doc = this.props.Document;
+ @observable _col: any = "";
@observable _menuWidth = 0;
@observable _headerOpen = false;
@observable _headerIsEditing = false;
- @observable _col: any = "";
@observable _menuHeight = 0;
@observable _pointerX = 0;
@observable _pointerY = 0;
@observable _openTypes: boolean = false;
+
+ @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
+ @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
+ @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); }
+ @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
+ @computed get scale() { return this.props.ScreenToLocalTransform().Scale; }
+ @computed get columns() { return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []); }
+ set columns(columns: SchemaHeaderField[]) { this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns); }
+
@computed get menuCoordinates() {
let searchx = 0;
let searchy = 0;
@@ -81,15 +83,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
return this.props.ScreenToLocalTransform().transformPoint(x, y);
}
- @computed get scale() { return this.props.ScreenToLocalTransform().Scale; }
-
- @computed get columns() {
- return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
- }
- set columns(columns: SchemaHeaderField[]) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns);
- }
-
get documentKeys() {
const docs = this.childDocs;
const keys: { [key: string]: boolean } = {};
@@ -104,27 +97,12 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
this.columns.forEach(key => keys[key.heading] = true);
return Array.from(Object.keys(keys));
}
- @computed get possibleKeys() { return this.documentKeys.filter(key => this.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1); }
@action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing;
-
- @action
- changeColumnType = (type: ColumnType, col: any): void => {
- this._openTypes = false;
- this.setColumnType(col, type);
- }
-
- changeColumnSort = (desc: boolean | undefined, col: any): void => {
- this.setColumnSort(col, desc);
- }
-
- changeColumnColor = (color: string, col: any): void => {
- this.setColumnColor(col, color);
- }
-
@undoBatch
setColumnType = (columnField: SchemaHeaderField, type: ColumnType): void => {
+ this._openTypes = false;
if (columnTypes.get(columnField.heading)) return;
const columns = this.columns;
@@ -165,42 +143,42 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
const type = col.type;
- const anyType = <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Any, col)}>
+ const anyType = <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Any)}>
<FontAwesomeIcon icon={"align-justify"} size="sm" />
Any
</div>;
- const numType = <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Number, col)}>
+ const numType = <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Number)}>
<FontAwesomeIcon icon={"hashtag"} size="sm" />
Number
</div>;
- const textType = <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.String, col)}>
+ const textType = <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.String)}>
<FontAwesomeIcon icon={"font"} size="sm" />
Text
</div>;
- const boolType = <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Boolean, col)}>
+ const boolType = <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Boolean)}>
<FontAwesomeIcon icon={"check-square"} size="sm" />
Checkbox
</div>;
- const listType = <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.List, col)}>
+ const listType = <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.List)}>
<FontAwesomeIcon icon={"list-ul"} size="sm" />
List
</div>;
- const docType = <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Doc, col)}>
+ const docType = <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Doc)}>
<FontAwesomeIcon icon={"file"} size="sm" />
Document
</div>;
- const imageType = <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Image, col)}>
+ const imageType = <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Image)}>
<FontAwesomeIcon icon={"image"} size="sm" />
Image
</div>;
- const dateType = <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Date, col)}>
+ const dateType = <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Date)}>
<FontAwesomeIcon icon={"calendar"} size="sm" />
Date
</div>;
@@ -239,15 +217,15 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
<div className="collectionSchema-headerMenu-group">
<label>Sort by:</label>
<div className="columnMenu-sort">
- <div className={"columnMenu-option" + (sort === true ? " active" : "")} onClick={() => this.changeColumnSort(true, col)}>
+ <div className={"columnMenu-option" + (sort === true ? " active" : "")} onClick={() => this.setColumnSort(col, true)}>
<FontAwesomeIcon icon="sort-amount-down" size="sm" />
Sort descending
</div>
- <div className={"columnMenu-option" + (sort === false ? " active" : "")} onClick={() => this.changeColumnSort(false, col)}>
+ <div className={"columnMenu-option" + (sort === false ? " active" : "")} onClick={() => this.setColumnSort(col, false)}>
<FontAwesomeIcon icon="sort-amount-up" size="sm" />
Sort ascending
</div>
- <div className="columnMenu-option" onClick={() => this.changeColumnSort(undefined, col)}>
+ <div className="columnMenu-option" onClick={() => this.setColumnSort(col, undefined)}>
<FontAwesomeIcon icon="times" size="sm" />
Clear sorting
</div>
@@ -270,12 +248,12 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
<div className="collectionSchema-headerMenu-group">
<label>Color:</label>
<div className="columnMenu-colors">
- <div className={"columnMenu-colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!, col)}></div>
- <div className={"columnMenu-colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!, col)}></div>
- <div className={"columnMenu-colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!, col)}></div>
- <div className={"columnMenu-colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!, col)}></div>
- <div className={"columnMenu-colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!, col)}></div>
- <div className={"columnMenu-colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray, col)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.setColumnColor(col, pink!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.setColumnColor(col, purple!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.setColumnColor(col, blue!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.setColumnColor(col, yellow!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.setColumnColor(col, red!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.setColumnColor(col, gray)}></div>
</div>
</div>
);
@@ -320,8 +298,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@action
closeHeader = () => { this._headerOpen = false; }
-
-
@undoBatch
@action
deleteColumn = (key: string) => {
@@ -351,15 +327,12 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
onWheel(e: React.WheelEvent) {
const scale = this.props.ScreenToLocalTransform().Scale;
this.props.active(true) && e.stopPropagation();
- //this.menuCoordinates[0] -= e.screenX / scale;
- //this.menuCoordinates[1] -= e.screenY / scale;
}
@computed get renderMenuContent() {
TraceMobx();
return <div className="collectionSchema-header-menuOptions">
{this.renderTypes(this._col)}
- {/* {this.renderSorting(this._col)} */}
{this.renderColors(this._col)}
<div className="collectionSchema-headerMenu-group">
<button onClick={() => { this.deleteColumn(this._col.heading); }}
@@ -379,7 +352,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@action setPreviewDoc = (doc: Opt<Doc>) => {
SelectionManager.SelectSchemaDoc(this, doc);
- this.previewDoc = doc;
+ this._previewDoc = doc;
}
//toggles preview side-panel of schema
@@ -389,7 +362,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
onDividerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, action(() => this.toggleExpander()));
+ setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander);
}
@action
onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
@@ -405,21 +378,19 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
onPointerDown = (e: React.PointerEvent): void => {
if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (this.props.isSelected(true)) e.stopPropagation();
- else {
- this.props.select(false);
- }
+ else this.props.select(false);
}
}
@computed
- get previewDocument(): Doc | undefined { return this.previewDoc; }
+ get previewDocument(): Doc | undefined { return this._previewDoc; }
@computed
get dividerDragger() {
return this.previewWidth() === 0 ? (null) :
- <div className="collectionSchemaView-dividerDragger"
- onPointerDown={this.onDividerDown}
- style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
+ <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} >
+ <div className="collectionSchemaView-dividerDragger" />
+ </div>;
}
@computed
@@ -501,18 +472,19 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
return <div className="collectionSchemaView-toolbar">
<div className="collectionSchemaView-toolbar-item">
<div id="preview-schema-checkbox-div">
- <input type="checkbox"
- key={"Show Preview"} checked={this.previewWidth() !== 0}
- onChange={this.toggleExpander} />Show Preview</div>
+ <input type="checkbox" key={"Show Preview"} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} />
+ Show Preview
+ </div>
</div>
</div>;
}
+
onSpecificMenu = (e: React.MouseEvent) => {
if ((e.target as any)?.className?.includes?.("collectionSchemaView-cell") || (e.target instanceof HTMLSpanElement)) {
const cm = ContextMenu.Instance;
const options = cm.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- optionItems.push({ description: "remove", event: () => this.previewDoc && this.props.removeDocument(this.previewDoc), icon: "trash" });
+ optionItems.push({ description: "remove", event: () => this._previewDoc && this.props.removeDocument(this._previewDoc), icon: "trash" });
!options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
cm.displayMenu(e.clientX, e.clientY);
(e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this.
@@ -558,39 +530,16 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
this.columns = columns;
}
- onZoomMenu = (e: React.WheelEvent) => {
- this.props.active(true) && e.stopPropagation();
- if (this.menuCoordinates[0] > e.screenX) {
- this.menuCoordinates[0] -= e.screenX; //* this.scale;
- } else {
- this.menuCoordinates[0] += e.screenX; //* this.scale;
- }
- if (this.menuCoordinates[1] > e.screenY) {
- this.menuCoordinates[1] -= e.screenY; //* this.scale;
- } else {
- this.menuCoordinates[1] += e.screenY; //* this.scale;
- }
- }
-
-
+ onZoomMenu = (e: React.WheelEvent) => this.props.active(true) && e.stopPropagation();
- onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
- }
render() {
- let name = "collectionSchemaView-container";
- if (this.props.Document._searchDoc) {
- name = "collectionSchemaView-searchContainer";
- }
- if (!this.props.active()) setTimeout(() => this.closeHeader(), 0);
TraceMobx();
+ if (!this.props.active()) setTimeout(() => this.closeHeader(), 0);
const menuContent = this.renderMenuContent;
const menu = <div className="collectionSchema-header-menu"
onWheel={e => this.onZoomMenu(e)}
onPointerDown={e => this.onHeaderClick(e)}
- style={{
- position: "fixed", background: "white", border: "black 1px solid",
- transform: `translate(${(this.menuCoordinates[0])}px, ${(this.menuCoordinates[1])}px)`
- }}>
+ style={{ transform: `translate(${(this.menuCoordinates[0])}px, ${(this.menuCoordinates[1])}px)` }}>
<Measure offset onResize={action((r: any) => {
const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height);
this._menuWidth = dim[0]; this._menuHeight = dim[1];
@@ -598,7 +547,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
{({ measureRef }) => <div ref={measureRef}> {menuContent} </div>}
</Measure>
</div>;
- return <div className={name}
+ return <div className={"collectionSchemaView" + (this.props.Document._searchDoc ? "-searchContainer" : "-container")}
style={{
overflow: this.props.overflow === true ? "scroll" : undefined, backgroundColor: "white",
pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined,
@@ -606,7 +555,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}} >
<div className="collectionSchemaView-tableContainer"
style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
- onKeyPress={this.onKeyPress}
onContextMenu={this.onSpecificMenu}
onPointerDown={this.onPointerDown}
onWheel={e => this.props.active(true) && e.stopPropagation()}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 1bc989e83..b7562c45e 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -131,8 +131,13 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
}
@action
- addDocument = (value: string, shiftDown?: boolean) => {
- if (!value) return false;
+ textCallback = (char: string) => {
+ return this.addDocument("", false, true);
+ }
+
+ @action
+ addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
+ if (!value && !forceEmptyNote) return false;
const key = StrCast(this.props.parent.props.Document._pivotField);
const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, title: value, _autoHeight: true });
newDoc[key] = this.getValue(this.props.heading);
@@ -140,7 +145,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
newDoc.heading = heading;
FormattedTextBox.SelectOnLoad = newDoc[Id];
- FormattedTextBox.SelectOnLoadChar = " ";
+ FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " ";
return this.props.parent.props.addDocument(newDoc);
}
@@ -300,6 +305,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
const newEditableViewProps = {
GetValue: () => "",
SetValue: this.addDocument,
+ textCallback: this.textCallback,
contents: "+ NEW",
HeadingObject: this.props.headingObject,
toggle: this.toggleVisibility,
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index fa80c8062..f3e563422 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -103,7 +103,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const { Document, DataDoc } = this.props;
const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.annotationsKey ? DataDoc : undefined, doc)).
filter(pair => { // filter out any documents that have a proto that we don't have permissions to (which we determine by not having any keys
- return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && Object.keys(pair.layout.proto).length));
+ return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));// Object.keys(pair.layout.proto).length));
});
return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
@@ -134,7 +134,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : [];
}
- const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
+ const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate).map(d => d as Doc);
const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
@@ -502,4 +502,5 @@ import { SelectionManager } from "../../util/SelectionManager";
import { OverlayView } from "../OverlayView";
import { setTimeout } from "timers";
import { Hypothesis } from "../../util/HypothesisUtils";
+import { GetEffectiveAcl } from "../../../fields/util";
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2bdc8e2f3..a27fa5a66 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -140,7 +140,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
return false;
}
else {
- if (this.props.Document[AclSym]) {
+ if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym])) {
added.forEach(d => {
for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true);
@@ -157,9 +157,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
});
}
else {
- added.map(doc => {
+ added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => { // only make a pushpin if we have acl's to edit the document
const context = Cast(doc.context, Doc, null);
- if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
+ const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context));
+ if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
const pushpin = Docs.Create.FontIconDocument({
title: "pushpin", label: "",
icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
@@ -186,9 +187,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
@action.bound
removeDocument = (doc: any): boolean => {
const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
- const docAcl = GetEffectiveAcl(doc);
- if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || docAcl === AclAdmin) {
- const docs = doc instanceof Doc ? [doc] : doc as Doc[];
+ const indocs = doc instanceof Doc ? [doc] : doc as Doc[];
+ const docs = indocs.filter(doc => effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin);
+ if (docs.length) {
const targetDataDoc = this.props.Document[DataSym];
const value = DocListCast(targetDataDoc[this.props.fieldKey]);
const toRemove = value.filter(v => docs.includes(v));
@@ -196,7 +197,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc;
toRemove.forEach(doc => {
const ind = (targetDataDoc[this.props.fieldKey] as List<Doc>).indexOf(doc);
- (targetDataDoc[this.props.fieldKey] as List<Doc>).splice(ind, 0);
if (ind !== -1) {
Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc);
recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index eda62bf0b..087d106c5 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -17,7 +17,7 @@ import { Docs, DocumentOptions } from "../../documents/Documents";
import { CompileScript, Transformer, ts } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
-import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
+import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../views/globalCssVariables.scss';
import { ContextMenu } from "../ContextMenu";
import '../DocumentDecorations.scss';
import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
@@ -90,8 +90,6 @@ export interface SchemaTableProps {
@observer
export class SchemaTable extends React.Component<SchemaTableProps> {
- private DIVIDER_WIDTH = 4;
-
@observable _cellIsEditing: boolean = false;
@observable _focusedCell: { row: number, col: number } = { row: 0, col: 0 };
@observable _openCollections: Set<number> = new Set;
@@ -104,7 +102,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
@computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
- @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH - this.previewWidth(); }
+ @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); }
@computed get childDocs() {
if (this.props.childDocs) return this.props.childDocs;
@@ -346,9 +344,11 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : "";
this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col);
- const pdoc = FieldValue(this.childDocs[this._focusedCell.row]);
- pdoc && this.props.setPreviewDoc(pdoc);
- e.stopPropagation();
+ if (direction) {
+ const pdoc = FieldValue(this.childDocs[this._focusedCell.row]);
+ pdoc && this.props.setPreviewDoc(pdoc);
+ e.stopPropagation();
+ }
} else if (e.keyCode === 27) {
this.props.setPreviewDoc(undefined);
e.stopPropagation(); // stopPropagation for left/right arrows
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 9dcfde7f9..4cf257640 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -22,74 +22,63 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
@observable _start = 0;
_anchorDisposer: IReactionDisposer | undefined;
_timeout: NodeJS.Timeout | undefined;
- componentWillUnmount() {
- this._anchorDisposer?.();
- }
- @action
- timeout = action(() => (Date.now() < this._start++ + 1000) && (this._timeout = setTimeout(this.timeout, 25)));
+ componentWillUnmount() { this._anchorDisposer?.(); }
+ @action timeout = action(() => (Date.now() < this._start++ + 1000) && (this._timeout = setTimeout(this.timeout, 25)));
componentDidMount() {
- this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)],
+ this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform()],
action(() => {
this._start = Date.now();
this._timeout && clearTimeout(this._timeout);
this._timeout = setTimeout(this.timeout, 25);
- if (SnappingManager.GetIsDragging() || !this.props.A.ContentDiv || !this.props.B.ContentDiv) return;
- setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
- setTimeout(action(() => (!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
- const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
- const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
- const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv);
- const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv);
- const a = adiv.getBoundingClientRect();
- const b = bdiv.getBoundingClientRect();
- const abounds = adiv.parentElement!.getBoundingClientRect();
- const bbounds = bdiv.parentElement!.getBoundingClientRect();
- const apt = Utils.closestPtBetweenRectangles(abounds.left, abounds.top, abounds.width, abounds.height,
- bbounds.left, bbounds.top, bbounds.width, bbounds.height,
- a.left + a.width / 2, a.top + a.height / 2);
- const bpt = Utils.closestPtBetweenRectangles(bbounds.left, bbounds.top, bbounds.width, bbounds.height,
- abounds.left, abounds.top, abounds.width, abounds.height,
- apt.point.x, apt.point.y);
- const afield = this.props.A.props.LayoutTemplateString?.indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
- const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
-
- // really hacky stuff to make the LinkAnchorBox display where we want it to:
- // if there's an element in the DOM with a classname containing the link's id and a data-targetids attribute containing the other end of the link,
- // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
- // otherwise, we just use the computed nearest point on the document boundary to the target Document
- const linkId = this.props.LinkDocs[0][Id]; // this link's Id
- const AanchorId = (this.props.LinkDocs[0][afield] as Doc)[Id]; // anchor a's id
- const BanchorId = (this.props.LinkDocs[0][bfield] as Doc)[Id]; // anchor b's id
- const linkEles = Array.from(window.document.getElementsByClassName(linkId));
- const targetAhyperlink = linkEles.find((ele: any) => ele.dataset.targetids?.includes(AanchorId));
- const targetBhyperlink = linkEles.find((ele: any) => ele.dataset.targetids?.includes(BanchorId));
- if (!targetBhyperlink) {
- 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.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.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.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.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.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);
- }
+ setTimeout(this.placeAnchors);
})
, { fireImmediately: true });
}
+ placeAnchors = () => {
+ const { A, B, LinkDocs } = this.props;
+ const linkDoc = LinkDocs[0];
+ if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return;
+ setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
+ setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
+ const acont = A.props.Document.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const bcont = B.props.Document.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const adiv = (acont.length ? acont[0] : A.ContentDiv);
+ const bdiv = (bcont.length ? bcont[0] : B.ContentDiv);
+ const a = adiv.getBoundingClientRect();
+ const b = bdiv.getBoundingClientRect();
+ const { left: aleft, top: atop, width: awidth, height: aheight } = adiv.parentElement!.getBoundingClientRect();
+ const { left: bleft, top: btop, width: bwidth, height: bheight } = bdiv.parentElement!.getBoundingClientRect();
+ const apt = Utils.closestPtBetweenRectangles(aleft, atop, awidth, aheight, bleft, btop, bwidth, bheight, a.left + a.width / 2, a.top + a.height / 2);
+ const bpt = Utils.closestPtBetweenRectangles(bleft, btop, bwidth, bheight, aleft, atop, awidth, aheight, apt.point.x, apt.point.y);
+ const afield = A.props.LayoutTemplateString?.indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
+ const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
+
+ // really hacky stuff to make the LinkAnchorBox display where we want it to:
+ // if there's an element in the DOM with a classname containing the link's id and a data-targetids attribute containing the other end of the link,
+ // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
+ // otherwise, we just use the computed nearest point on the document boundary to the target Document
+ const linkEles = Array.from(window.document.getElementsByClassName(linkDoc[Id]));
+ const targetAhyperlink = linkEles.find((ele: any) => ele.dataset.targetids?.includes((linkDoc[afield] as Doc)[Id]));
+ const targetBhyperlink = linkEles.find((ele: any) => ele.dataset.targetids?.includes((linkDoc[bfield] as Doc)[Id]));
+ if (!targetBhyperlink) {
+ A.rootDoc[afield + "_x"] = (apt.point.x - aleft) / awidth * 100;
+ A.rootDoc[afield + "_y"] = (apt.point.y - atop) / aheight * 100;
+ } else {
+ const m = targetBhyperlink.getBoundingClientRect();
+ const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+ A.rootDoc[afield + "_x"] = Math.min(1, mp[0] / A.props.PanelWidth()) * 100;
+ A.rootDoc[afield + "_y"] = Math.min(1, mp[1] / A.props.PanelHeight()) * 100;
+ }
+ if (!targetAhyperlink) {
+ B.rootDoc[bfield + "_x"] = (bpt.point.x - bleft) / bwidth * 100;
+ B.rootDoc[bfield + "_y"] = (bpt.point.y - btop) / bheight * 100;
+ } else {
+ const m = targetAhyperlink.getBoundingClientRect();
+ const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+ B.rootDoc[bfield + "_x"] = Math.min(1, mp[0] / B.props.PanelWidth()) * 100;
+ B.rootDoc[bfield + "_y"] = Math.min(1, mp[1] / B.props.PanelHeight()) * 100;
+ }
+ }
pointerDown = (e: React.PointerEvent) => {
@@ -111,13 +100,15 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
let rect = el.getBoundingClientRect();
const top = rect.top, height = rect.height;
var el = el.parentNode;
- do {
- rect = el.getBoundingClientRect();
- if (top <= rect.right === false && getComputedStyle(el).overflow === "hidden") return rect.bottom;
- // Check if the element is out of view due to a container scrolling
- if ((top + height) <= rect.top && getComputedStyle(el).overflow === "hidden") return rect.top;
+ while (el && el !== document.body) {
+ if (el.hasOwnProperty("getBoundingClientRect")) {
+ rect = el.getBoundingClientRect();
+ if (top <= rect.bottom === false && getComputedStyle(el).overflow === "hidden") return rect.bottom;
+ // Check if the element is out of view due to a container scrolling
+ if ((top + height) <= rect.top && getComputedStyle(el).overflow === "hidden") return rect.top;
+ }
el = el.parentNode;
- } while (el !== document.body);
+ }
// Check its within the document viewport
return top; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden";
}
@@ -125,27 +116,27 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
let rect = el.getBoundingClientRect();
const left = rect.left, width = rect.width;
var el = el.parentNode;
- do {
- rect = el.getBoundingClientRect();
- if (left <= rect.right === false && getComputedStyle(el).overflow === "hidden") return rect.right;
- // Check if the element is out of view due to a container scrolling
- if ((left + width) <= rect.left && getComputedStyle(el).overflow === "hidden") return rect.left;
+ while (el && el !== document.body) {
+ if (el.hasOwnProperty("getBoundingClientRect")) {
+ rect = el.getBoundingClientRect();
+ if (left <= rect.right === false && getComputedStyle(el).overflow === "hidden") return rect.right;
+ // Check if the element is out of view due to a container scrolling
+ if ((left + width) <= rect.left && getComputedStyle(el).overflow === "hidden") return rect.left;
+ }
el = el.parentNode;
- } while (el !== document.body);
+ }
// Check its within the document viewport
return left; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden";
}
@computed.struct get renderData() {
this._start; SnappingManager.GetIsDragging();
- if (!this.props.A.ContentDiv || !this.props.B.ContentDiv || !this.props.LinkDocs.length) {
- return undefined;
- }
- this.props.A.props.ScreenToLocalTransform().transform(this.props.B.props.ScreenToLocalTransform());
- const acont = this.props.A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
- const bcont = this.props.B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
- const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv);
- const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv);
+ const { A, B, LinkDocs } = this.props;
+ if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined;
+ const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const adiv = (acont.length ? acont[0] : A.ContentDiv);
+ const bdiv = (bcont.length ? bcont[0] : B.ContentDiv);
for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return;
for (let apdiv = bdiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return;
const a = adiv.getBoundingClientRect();
@@ -154,12 +145,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const btop = this.visibleY(bdiv);
const aleft = this.visibleX(adiv);
const bleft = this.visibleX(bdiv);
- const apt = Utils.closestPtBetweenRectangles(aleft, atop, a.width, a.height,
- bleft, btop, b.width, b.height,
- a.left + a.width / 2, a.top + a.height / 2);
- const bpt = Utils.closestPtBetweenRectangles(bleft, btop, b.width, b.height,
- aleft, atop, a.width, a.height,
- apt.point.x, apt.point.y);
+ const clipped = aleft !== a.left || atop !== a.top || bleft !== b.left || btop !== b.top;
+ const apt = Utils.closestPtBetweenRectangles(aleft, atop, a.width, a.height, bleft, btop, b.width, b.height, a.left + a.width / 2, a.top + a.height / 2);
+ const bpt = Utils.closestPtBetweenRectangles(bleft, btop, b.width, b.height, aleft, atop, a.width, a.height, apt.point.x, apt.point.y);
const pt1 = [apt.point.x, apt.point.y];
const pt2 = [bpt.point.x, bpt.point.y];
const pt1vec = [pt1[0] - (aleft + a.width / 2), pt1[1] - (atop + a.height / 2)];
@@ -167,14 +155,13 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const pt1len = Math.sqrt((pt1vec[0] * pt1vec[0]) + (pt1vec[1] * pt1vec[1]));
const pt2len = Math.sqrt((pt2vec[0] * pt2vec[0]) + (pt2vec[1] * pt2vec[1]));
const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2;
- const pt1norm = [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen];
- const pt2norm = [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen];
- const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
- const bActive = this.props.B.isSelected() || Doc.IsBrushed(this.props.B.props.Document);
- if (aleft !== a.left || atop !== a.top || bleft !== b.left || btop !== b.top) return { a, b, pt1norm: [0, 0], pt2norm: [0, 0], aActive, bActive, textX: undefined, textY: undefined, pt1, pt2 };
+ const pt1norm = clipped ? [0, 0] : [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen];
+ const pt2norm = clipped ? [0, 0] : [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen];
+ const aActive = A.isSelected() || Doc.IsBrushed(A.props.Document);
+ const bActive = B.isSelected() || Doc.IsBrushed(B.props.Document);
- const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(this.props.LinkDocs[0].linkOffsetX);
- const textY = (pt1[1] + pt2[1]) / 2 + NumCast(this.props.LinkDocs[0].linkOffsetY);
+ const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].linkOffsetX);
+ const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].linkOffsetY);
return { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 };
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2783011cf..71519f2b9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -7,7 +7,7 @@ import { Id } from "../../../../fields/FieldSymbols";
import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { RichTextField } from "../../../../fields/RichTextField";
-import { createSchema, makeInterface } from "../../../../fields/Schema";
+import { createSchema, makeInterface, listSpec } from "../../../../fields/Schema";
import { ScriptField } from "../../../../fields/ScriptField";
import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
import { TraceMobx } from "../../../../fields/util";
@@ -88,6 +88,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private _clusterDistance: number = 75;
private _hitCluster = false;
private _layoutComputeReaction: IReactionDisposer | undefined;
+ private _boundsReaction: IReactionDisposer | undefined;
private _layoutPoolData = new ObservableMap<string, PoolData>();
private _layoutSizeData = new ObservableMap<string, { width?: number, height?: number }>();
private _cachedPool: Map<string, PoolData> = new Map();
@@ -782,10 +783,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05;
if (deltaScale < 0) deltaScale = -deltaScale;
const [x, y] = this.getTransform().transformPoint(pointX, pointY);
+ const invTransform = this.getLocalTransform().inverse();
+ if (deltaScale * invTransform.Scale > 20) {
+ deltaScale = 20 / invTransform.Scale;
+ }
const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
if (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling()) {
- const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
+ const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 20);
this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
}
@@ -867,7 +872,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
}
- focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => {
+ focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean, dontCenter?: boolean) => {
const state = HistoryUtil.getState();
// TODO This technically isn't correct if type !== "doc", as
@@ -886,15 +891,32 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
SelectionManager.DeselectAll();
if (this.props.Document.scrollHeight) {
const annotOn = Cast(doc.annotationOn, Doc) as Doc;
+ let delay = 1000;
if (!annotOn) {
- this.props.focus(doc);
+ !dontCenter && this.props.focus(doc);
+ afterFocus && setTimeout(afterFocus, delay);
} else {
const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
- const offset = annotOn && (contextHgt / 2);
- this.props.Document._scrollY = NumCast(doc.y) - offset;
+ const curScroll = NumCast(this.props.Document._scrollTop);
+ let scrollTo = curScroll;
+ if (curScroll + contextHgt < NumCast(doc.y)) {
+ scrollTo = NumCast(doc.y) + Math.max(NumCast(doc._height), 100) - contextHgt;
+ } else if (curScroll > NumCast(doc.y)) {
+ scrollTo = Math.max(0, NumCast(doc.y) - 50);
+ }
+ if (curScroll !== scrollTo || this.props.Document._viewTransition) {
+ this.props.Document._scrollPY = this.props.Document._scrollY = scrollTo;
+ delay = Math.abs(scrollTo - curScroll) > 5 ? 1000 : 0;
+ !dontCenter && this.props.focus(this.props.Document);
+ afterFocus && setTimeout(afterFocus, delay);
+ } else {
+ !dontCenter && delay && this.props.focus(this.props.Document);
+ // @ts-ignore
+ afterFocus(true); // bcz: TODO Aragh -- need to add a parameter to afterFocus() functions to indicate whether the focus function didn't need to scroll
+
+ }
}
- afterFocus && setTimeout(afterFocus, 1000);
} else {
const layoutdoc = Doc.Layout(doc);
const newPanX = NumCast(doc.x) + NumCast(layoutdoc._width) / 2;
@@ -914,14 +936,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
willZoom && this.setScaleToZoom(layoutdoc, scale);
Doc.linkFollowHighlight(doc);
+ const notFocused = newPanX === savedState.px && newPanY === savedState.py;
afterFocus && setTimeout(() => {
- if (afterFocus?.()) {
+ // @ts-ignore
+ if (afterFocus?.(notFocused)) { // bcz: TODO Aragh -- need to add a parameter to afterFocus() functions to indicate whether the focus function didn't need to scroll
this.Document._panX = savedState.px;
this.Document._panY = savedState.py;
this.Document[this.scaleFieldKey] = savedState.s;
this.Document._viewTransition = savedState.pt;
}
- }, 500);
+ }, notFocused ? 0 : 500);
}
}
@@ -1145,12 +1169,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
(elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
+ if (!this.props.annotationsKey) {
+ this._boundsReaction = reaction(() => this.contentBounds,
+ bounds => (!this.fitToContent && this._layoutElements?.length) && setTimeout(() => {
+ const rbounds = Cast(this.Document._renderContentBounds, listSpec("number"), [0, 0, 0, 0]);
+ if (rbounds[0] !== bounds.x || rbounds[1] !== bounds.y || rbounds[2] !== bounds.r || rbounds[3] !== bounds.b) {
+ this.Document._renderContentBounds = new List<number>([bounds.x, bounds.y, bounds.r, bounds.b]);
+ }
+ }));
+ }
this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
}
componentWillUnmount() {
this._layoutComputeReaction?.();
+ this._boundsReaction?.();
this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
}
@@ -1159,7 +1193,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
onCursorMove = (e: React.PointerEvent) => {
- super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
+ // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
@@ -1424,10 +1458,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return wscale < hscale ? wscale : hscale;
}
@computed get backgroundEvents() { return this.layoutDoc._isBackground && SnappingManager.GetIsDragging(); }
+
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
- !this.fitToContent && this._layoutElements?.length && setTimeout(() => this.Document._renderContentBounds = new List<number>([this.contentBounds.x, this.contentBounds.y, this.contentBounds.r, this.contentBounds.b]), 0);
return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget}
onPointerOver={this.onPointerOver}
onWheel={this.onPointerWheel}