aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionStackingView.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections/CollectionStackingView.tsx')
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx146
1 files changed, 79 insertions, 67 deletions
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 91c7ca76e..076dd3629 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -9,61 +9,61 @@ import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../new_fields/Types";
-import { emptyFunction, Utils } from "../../../Utils";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { DragManager } from "../../util/DragManager";
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
+import { TraceMobx } from "../../../new_fields/util";
+import { Utils, setupMoveUpEvents, emptyFunction } from "../../../Utils";
+import { DragManager, dropActionType } from "../../util/DragManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
import { EditableView } from "../EditableView";
import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
+import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
import "./CollectionStackingView.scss";
import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
import { CollectionSubView } from "./CollectionSubView";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
-import { TraceMobx } from "../../../new_fields/util";
import { CollectionViewType } from "./CollectionView";
+import { Docs } from "../../documents/Documents";
@observer
export class CollectionStackingView extends CollectionSubView(doc => doc) {
_masonryGridRef: HTMLDivElement | null = null;
_draggerRef = React.createRef<HTMLDivElement>();
_heightDisposer?: IReactionDisposer;
- _sectionFilterDisposer?: IReactionDisposer;
+ _pivotFieldDisposer?: IReactionDisposer;
_docXfs: any[] = [];
_columnStart: number = 0;
@observable _heightMap = new Map<string, number>();
@observable _cursor: CursorProperty = "grab";
@observable _scroll = 0; // used to force the document decoration to update when scrolling
@computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
- @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
- @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout); }
- @computed get xMargin() { return NumCast(this.props.Document._xMargin, 2 * this.gridGap); }
- @computed get yMargin() { return Math.max(this.props.Document.showTitle && !this.props.Document.showTitleHover ? 30 : 0, NumCast(this.props.Document._yMargin, 2 * this.gridGap)); }
+ @computed get pivotField() { return StrCast(this.props.Document._pivotField); }
+ @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); }
+ @computed get xMargin() { return NumCast(this.props.Document._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
+ @computed get yMargin() { return Math.max(this.props.Document._showTitle && !this.props.Document._showTitleHover ? 30 : 0, NumCast(this.props.Document._yMargin, 0)); } // 2 * this.gridGap)); }
@computed get gridGap() { return NumCast(this.props.Document._gridGap, 10); }
@computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); }
@computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
- @computed get showAddAGroup() { return (this.sectionFilter && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); }
+ @computed get showAddAGroup() { return (this.pivotField && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); }
@computed get columnWidth() {
return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin,
this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250));
}
@computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
- children(docs: Doc[]) {
+ children(docs: Doc[], columns?: number) {
this._docXfs.length = 0;
return docs.map((d, i) => {
- const width = () => Math.min(d._nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
const height = () => this.getDocHeight(d);
+ const width = () => this.getDocWidth(d);
const dref = React.createRef<HTMLDivElement>();
const dxf = () => this.getDocTransform(d, dref.current!);
this._docXfs.push({ dxf: dxf, width: width, height: height });
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
- const style = this.isStackingView ? { width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` };
+ const style = this.isStackingView ? { width: width(), marginTop: this.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` };
return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
- {this.getDisplayDoc(d, Cast(d.resolvedDataDoc, Doc, null) || this.props.DataDoc, dxf, width)}
+ {this.getDisplayDoc(d, (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc, dxf, width)}
</div>;
});
}
@@ -73,31 +73,42 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
get Sections() {
- if (!this.sectionFilter || this.sectionHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
+ if (!this.pivotField || this.sectionHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
if (this.sectionHeaders === undefined) {
setTimeout(() => this.props.Document.sectionHeaders = new List<SchemaHeaderField>(), 0);
return new Map<SchemaHeaderField, Doc[]>();
}
- const sectionHeaders = this.sectionHeaders;
+ const sectionHeaders: SchemaHeaderField[] = Array.from(this.sectionHeaders);
const fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
+ let changed = false;
this.filteredChildren.map(d => {
- const sectionValue = (d[this.sectionFilter] ? d[this.sectionFilter] : `NO ${this.sectionFilter.toUpperCase()} VALUE`) as object;
+ const sectionValue = (d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`) as object;
// the next five lines ensures that floating point rounding errors don't create more than one section -syip
const parsed = parseInt(sectionValue.toString());
const castedSectionValue = !isNaN(parsed) ? parsed : sectionValue;
// look for if header exists already
- const existingHeader = sectionHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`));
+ const existingHeader = sectionHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`));
if (existingHeader) {
fields.get(existingHeader)!.push(d);
}
else {
- const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`);
+ const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`);
fields.set(newSchemaHeader, [d]);
sectionHeaders.push(newSchemaHeader);
+ changed = true;
}
});
+ // remove all empty columns if hideHeadings is set
+ if (this.props.Document.hideHeadings) {
+ Array.from(fields.keys()).filter(key => !fields.get(key)!.length).map(header => {
+ fields.delete(header);
+ sectionHeaders.splice(sectionHeaders.indexOf(header), 1);
+ changed = true;
+ });
+ }
+ changed && setTimeout(action(() => { if (this.sectionHeaders) { this.sectionHeaders.length = 0; this.sectionHeaders.push(...sectionHeaders); } }), 0);
return fields;
}
@@ -131,15 +142,15 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
);
// reset section headers when a new filter is inputted
- this._sectionFilterDisposer = reaction(
- () => this.sectionFilter,
+ this._pivotFieldDisposer = reaction(
+ () => this.pivotField,
() => this.props.Document.sectionHeaders = new List()
);
}
componentWillUnmount() {
super.componentWillUnmount();
- this._heightDisposer && this._heightDisposer();
- this._sectionFilterDisposer && this._sectionFilterDisposer();
+ this._heightDisposer?.();
+ this._pivotFieldDisposer?.();
}
@action
@@ -155,14 +166,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@computed get onClickHandler() { return ScriptCast(this.Document.onChildClick); }
getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
- const layoutDoc = Doc.Layout(doc);
+ const layoutDoc = Doc.Layout(doc, this.props.childLayoutTemplate?.());
const height = () => this.getDocHeight(doc);
return <ContentFittingDocumentView
Document={doc}
- DataDocument={dataDoc}
+ DataDocument={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
+ backgroundColor={this.props.backgroundColor}
+ LayoutDoc={this.props.childLayoutTemplate}
LibraryPath={this.props.LibraryPath}
renderDepth={this.props.renderDepth + 1}
fitToBox={this.props.fitToBox}
+ dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
PanelWidth={width}
PanelHeight={height}
@@ -179,42 +193,36 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
pinToPres={this.props.pinToPres}>
</ContentFittingDocumentView>;
}
+
+ getDocWidth(d?: Doc) {
+ if (!d) return 0;
+ const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+ const nw = NumCast(layoutDoc._nativeWidth);
+ return Math.min(nw && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
+ }
getDocHeight(d?: Doc) {
if (!d) return 0;
- const layoutDoc = Doc.Layout(d);
+ const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
const nw = NumCast(layoutDoc._nativeWidth);
const nh = NumCast(layoutDoc._nativeHeight);
let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
- if (!layoutDoc.ignoreAspect && !layoutDoc._fitWidth && nw && nh) {
+ if (!layoutDoc._fitWidth && nw && nh) {
const aspect = nw && nh ? nh / nw : 1;
- if (!(d._nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
+ if (!(this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
return wid * aspect;
}
- return layoutDoc._fitWidth ? !layoutDoc._nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin :
- Math.min(wid * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym]();
+ return layoutDoc._fitWidth ? !nh ? this.props.PanelHeight() - 2 * this.yMargin :
+ Math.min(wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym]();
}
columnDividerDown = (e: React.PointerEvent) => {
- e.stopPropagation();
- e.preventDefault();
runInAction(() => this._cursor = "grabbing");
- document.addEventListener("pointermove", this.onDividerMove);
- document.addEventListener('pointerup', this.onDividerUp);
- this._columnStart = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
- }
- @action
- onDividerMove = (e: PointerEvent): void => {
- const dragPos = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
- const delta = dragPos - this._columnStart;
- this._columnStart = dragPos;
- this.layoutDoc.columnWidth = Math.max(10, this.columnWidth + delta);
+ setupMoveUpEvents(this, e, this.onDividerMove, action(() => this._cursor = "grab"), emptyFunction);
}
-
@action
- onDividerUp = (e: PointerEvent): void => {
- runInAction(() => this._cursor = "grab");
- document.removeEventListener("pointermove", this.onDividerMove);
- document.removeEventListener('pointerup', this.onDividerUp);
+ onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ this.layoutDoc.columnWidth = Math.max(10, this.columnWidth + delta[0]);
+ return false;
}
@computed get columnDragger() {
@@ -226,7 +234,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@undoBatch
@action
- drop = (e: Event, de: DragManager.DropEvent) => {
+ onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
const where = [de.x, de.y];
let targInd = -1;
let plusOne = 0;
@@ -240,7 +248,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
plusOne = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0;
}
});
- if (super.drop(e, de)) {
+ if (super.onInternalDrop(e, de)) {
const newDoc = de.complete.docDragData.droppedDocuments[0];
const docs = this.childDocList;
if (docs) {
@@ -256,7 +264,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
@undoBatch
@action
- onDrop = async (e: React.DragEvent): Promise<void> => {
+ onExternalDrop = async (e: React.DragEvent): Promise<void> => {
const where = [e.clientX, e.clientY];
let targInd = -1;
this._docXfs.map((cd, i) => {
@@ -266,7 +274,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
targInd = i;
}
});
- super.onDrop(e, {}, () => {
+ super.onExternalDrop(e, {}, () => {
if (targInd !== -1) {
const newDoc = this.childDocs[this.childDocs.length - 1];
const docs = this.childDocList;
@@ -277,9 +285,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
});
}
- headings = () => Array.from(this.Sections.keys());
+ headings = () => Array.from(this.Sections);
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
- const key = this.sectionFilter;
+ const key = this.pivotField;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
@@ -314,7 +322,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
- const key = this.sectionFilter;
+ const key = this.pivotField;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
@@ -340,7 +348,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@action
addGroup = (value: string) => {
if (value && this.sectionHeaders) {
- this.sectionHeaders.push(new SchemaHeaderField(value));
+ const schemaHdrField = new SchemaHeaderField(value);
+ this.sectionHeaders.push(schemaHdrField);
+ Doc.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: schemaHdrField.color }]);
return true;
}
return false;
@@ -362,8 +372,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
if (!e.isPropagationStopped()) {
const subItems: ContextMenuProps[] = [];
subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" });
- subItems.push({ description: `${this.props.Document.showTitles ? "Hide Titles" : "Show Titles"}`, event: () => this.props.Document.showTitles = !this.props.Document.showTitles ? "title" : "", icon: "plus" });
- subItems.push({ description: `${this.props.Document.showCaptions ? "Hide Captions" : "Show Captions"}`, event: () => this.props.Document.showCaptions = !this.props.Document.showCaptions ? "caption" : "", icon: "plus" });
ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" });
}
}
@@ -371,12 +379,15 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@computed get renderedSections() {
TraceMobx();
let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]];
- if (this.sectionFilter) {
+ if (this.pivotField) {
const entries = Array.from(this.Sections.entries());
sections = entries.sort(this.sortFunc);
}
return sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]));
}
+
+ @computed get scaling() { return !this.props.Document._nativeWidth ? 1 : this.props.PanelHeight() / NumCast(this.props.Document._nativeHeight); }
+
render() {
TraceMobx();
const editableViewProps = {
@@ -390,12 +401,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
ref={this.createRef}
style={{
overflowY: this.props.active() ? "auto" : "hidden",
- transform: `scale(${Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]())})`,
- height: `${Math.max(100, 100 * 1 / Math.min(this.props.PanelWidth() / this.layoutDoc[WidthSym](), this.props.PanelHeight() / this.layoutDoc[HeightSym]()))}%`,
- transformOrigin: "top"
+ transform: `scale(${this.scaling}`,
+ height: `${1 / this.scaling * 100}%`,
+ width: `${1 / this.scaling * 100}%`,
+ transformOrigin: "top left",
}}
onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
- onDrop={this.onDrop.bind(this)}
+ onDrop={this.onExternalDrop.bind(this)}
onContextMenu={this.onContextMenu}
onWheel={e => this.props.active() && e.stopPropagation()} >
{this.renderedSections}
@@ -404,7 +416,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
<EditableView {...editableViewProps} />
</div>}
- {this.props.Document._chromeStatus !== 'disabled' ? <Switch
+ {this.props.Document._chromeStatus !== 'disabled' && this.props.isSelected() ? <Switch
onChange={this.onToggle}
onClick={this.onToggle}
defaultChecked={this.props.Document._chromeStatus !== 'view-mode'}