aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authorStanley Yip <stanley_yip@brown.edu>2020-01-17 15:45:05 -0500
committerStanley Yip <stanley_yip@brown.edu>2020-01-17 15:45:05 -0500
commit8667498929fce14295658f89c8787a1a9b1ce468 (patch)
treec3d03768bf2b8d23e438479c67141fae43c63e1e /src/client/views/collections
parent4b0a056f1afaf20dea4be64c7b238748d99ad12e (diff)
parent9620d149a2051bc9b42a037b1c65b61deebd11fa (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into pen
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx44
-rw-r--r--src/client/views/collections/CollectionLinearView.scss75
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx120
-rw-r--r--src/client/views/collections/CollectionMulticolumnView.scss23
-rw-r--r--src/client/views/collections/CollectionMulticolumnView.tsx298
-rw-r--r--src/client/views/collections/CollectionPivotView.scss57
-rw-r--r--src/client/views/collections/CollectionPivotView.tsx118
-rw-r--r--src/client/views/collections/CollectionSchemaHeaders.tsx3
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx45
-rw-r--r--src/client/views/collections/CollectionSubView.tsx6
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx58
-rw-r--r--src/client/views/collections/CollectionView.tsx55
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx7
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx15
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx37
17 files changed, 877 insertions, 98 deletions
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 151b84c50..022eccc13 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -34,6 +34,7 @@ import { DocumentType } from '../../documents/DocumentTypes';
import { ComputedField } from '../../../new_fields/ScriptField';
import { InteractionUtils } from '../../util/InteractionUtils';
import { TraceMobx } from '../../../new_fields/util';
+import { Scripting } from '../../util/Scripting';
library.add(faFile);
const _global = (window /* browser */ || global /* node */) as any;
@@ -177,7 +178,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
//
@undoBatch
@action
- public static AddRightSplit(document: Doc, dataDoc: Doc | undefined, minimize: boolean = false, libraryPath?: Doc[]) {
+ public static AddRightSplit(document: Doc, dataDoc: Doc | undefined, libraryPath?: Doc[]) {
if (!CollectionDockingView.Instance) return false;
const instance = CollectionDockingView.Instance;
const newItemStackConfig = {
@@ -202,15 +203,42 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
collayout.config.width = 50;
newContentItem.config.width = 50;
}
- if (minimize) {
- // bcz: this makes the drag image show up better, but it also messes with fixed layout sizes
- // newContentItem.config.width = 10;
- // newContentItem.config.height = 10;
- }
newContentItem.callDownwards('_$init');
instance.layoutChanged();
return true;
}
+ //
+ // Creates a vertical split on the right side of the docking view, and then adds the Document to that split
+ //
+ @undoBatch
+ @action
+ public static UseRightSplit(document: Doc, dataDoc: Doc | undefined, libraryPath?: Doc[]) {
+ if (!CollectionDockingView.Instance) return false;
+ const instance = CollectionDockingView.Instance;
+ if (instance._goldenLayout.root.contentItems[0].isRow) {
+ let found: DocumentView | undefined;
+ Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
+ if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
+ DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)?.props.Document.isDisplayPanel) {
+ found = DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!;
+ } else {
+ Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
+ if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)?.props.Document.isDisplayPanel) {
+ found = DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!;
+ return true;
+ }
+ return false;
+ });
+ }
+ });
+ if (found) {
+ Doc.GetProto(found.props.Document).data = new List<Doc>([document]);
+ } else {
+ const stackView = Docs.Create.FreeformDocument([document], { fitToBox: true, isDisplayPanel: true, title: "document viewer" });
+ CollectionDockingView.AddRightSplit(stackView, undefined, []);
+ }
+ }
+ }
@undoBatch
@action
@@ -674,7 +702,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
if (doc.dockingConfig) {
return MainView.Instance.openWorkspace(doc);
} else if (location === "onRight") {
- return CollectionDockingView.AddRightSplit(doc, dataDoc, undefined, libraryPath);
+ return CollectionDockingView.AddRightSplit(doc, dataDoc, libraryPath);
} else if (location === "close") {
return CollectionDockingView.CloseRightSplit(doc);
} else {
@@ -724,3 +752,5 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
</div >);
}
}
+Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc, undefined); });
+Scripting.addGlobal(function useRightSplit(doc: any) { CollectionDockingView.UseRightSplit(doc, undefined); });
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
new file mode 100644
index 000000000..eae9e0220
--- /dev/null
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -0,0 +1,75 @@
+@import "../globalCssVariables";
+@import "../_nodeModuleOverrides";
+
+.collectionLinearView-outer{
+ overflow: hidden;
+ height:100%;
+ .collectionLinearView {
+ display:flex;
+ height: 100%;
+ >label {
+ background: $dark-color;
+ color: $light-color;
+ display: inline-block;
+ border-radius: 18px;
+ font-size: 12.5px;
+ width: 18px;
+ height: 18px;
+ margin-top:auto;
+ margin-bottom:auto;
+ margin-right: 3px;
+ cursor: pointer;
+ transition: transform 0.2s;
+ }
+
+ label p {
+ padding-left:5px;
+ }
+
+ label:hover {
+ background: $main-accent;
+ transform: scale(1.15);
+ }
+
+ >input {
+ display: none;
+ }
+ >input:not(:checked)~.collectionLinearView-content {
+ display: none;
+ }
+
+ >input:checked~label {
+ transform: rotate(45deg);
+ transition: transform 0.5s;
+ cursor: pointer;
+ }
+
+ .collectionLinearView-content {
+ display: flex;
+ opacity: 1;
+ position: relative;
+ margin-top: auto;
+
+ .collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable {
+ position:relative;
+ margin:auto;
+ margin-left: 3px;
+ transform-origin: center 80%;
+ }
+ .collectionLinearView-docBtn-scalable:hover {
+ transform: scale(1.15);
+ }
+
+ .collectionLinearView-round-button {
+ width: 18px;
+ height: 18px;
+ border-radius: 18px;
+ font-size: 15px;
+ }
+
+ .collectionLinearView-round-button:hover {
+ transform: scale(1.15);
+ }
+ }
+ }
+}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
new file mode 100644
index 000000000..e91c2a01d
--- /dev/null
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -0,0 +1,120 @@
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc';
+import { makeInterface } from '../../../new_fields/Schema';
+import { BoolCast, NumCast, StrCast, Cast } from '../../../new_fields/Types';
+import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../../Utils';
+import { DragManager } from '../../util/DragManager';
+import { Transform } from '../../util/Transform';
+import "./CollectionLinearView.scss";
+import { CollectionViewType } from './CollectionView';
+import { CollectionSubView } from './CollectionSubView';
+import { DocumentView } from '../nodes/DocumentView';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { Id } from '../../../new_fields/FieldSymbols';
+import { ScriptField } from '../../../new_fields/ScriptField';
+
+
+type LinearDocument = makeInterface<[typeof documentSchema,]>;
+const LinearDocument = makeInterface(documentSchema);
+
+@observer
+export class CollectionLinearView extends CollectionSubView(LinearDocument) {
+ @observable public addMenuToggle = React.createRef<HTMLInputElement>();
+ @observable private _selectedIndex = -1;
+ private _dropDisposer?: DragManager.DragDropDisposer;
+ private _widthDisposer?: IReactionDisposer;
+ private _selectedDisposer?: IReactionDisposer;
+
+ componentWillUnmount() {
+ this._dropDisposer && this._dropDisposer();
+ this._widthDisposer && this._widthDisposer();
+ this._selectedDisposer && this._selectedDisposer();
+ }
+
+ 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(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
+ () => this.props.Document.width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+ { fireImmediately: true }
+ );
+
+ this._selectedDisposer = reaction(
+ () => NumCast(this.props.Document.selectedIndex),
+ (i) => runInAction(() => this._selectedIndex = i),
+ { fireImmediately: true }
+ );
+ }
+ protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ this._dropDisposer && this._dropDisposer();
+ if (ele) {
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+ }
+ }
+
+ public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
+
+ dimension = () => NumCast(this.props.Document.height); // 2 * the padding
+ getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
+ if (!ele.current) return Transform.Identity();
+ const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
+ return new Transform(-translateX, -translateY, 1 / scale);
+ }
+
+ render() {
+ const guid = Utils.GenerateGuid();
+ return <div className="collectionLinearView-outer">
+ <div className="collectionLinearView" ref={this.createDashEventsTarget} >
+ <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle}
+ onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
+ <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
+
+ <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document.width, 25) }}>
+ {this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => {
+ const nested = pair.layout.viewType === CollectionViewType.Linear;
+ const dref = React.createRef<HTMLDivElement>();
+ const nativeWidth = NumCast(pair.layout.nativeWidth, this.dimension());
+ const deltaSize = nativeWidth * .15 / 2;
+ const isSelected = this._selectedIndex === ind;
+ if (isSelected) {
+ Cast(pair.layout.proto?.onPointerDown, ScriptField)?.script.run({ this: pair.layout.proto }, console.log);
+ }
+ return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
+ style={{
+ width: nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize,
+ height: nested && pair.layout.isExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
+ }} >
+ <DocumentView
+ Document={pair.layout}
+ DataDoc={pair.data}
+ LibraryPath={this.props.LibraryPath}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={emptyFunction}
+ removeDocument={this.props.removeDocument}
+ ruleProvider={undefined}
+ onClick={undefined}
+ ScreenToLocalTransform={this.getTransform(dref)}
+ ContentScaling={returnOne}
+ PanelWidth={nested ? pair.layout[WidthSym] : () => this.dimension()}// ugh - need to get rid of this inline function to avoid recomputing
+ PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()}
+ renderDepth={this.props.renderDepth + 1}
+ focus={emptyFunction}
+ backgroundColor={returnEmptyString}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ zoomToScale={emptyFunction}
+ getScale={returnOne}>
+ </DocumentView>
+ </div>;
+ })}
+ </div>
+ </div>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMulticolumnView.scss b/src/client/views/collections/CollectionMulticolumnView.scss
new file mode 100644
index 000000000..a54af748b
--- /dev/null
+++ b/src/client/views/collections/CollectionMulticolumnView.scss
@@ -0,0 +1,23 @@
+.collectionMulticolumnView_contents {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+
+ .document-wrapper {
+ display: flex;
+ flex-direction: column;
+
+ .display {
+ text-align: center;
+ height: 20px;
+ }
+
+ }
+
+ .resizer {
+ background: black;
+ cursor: ew-resize;
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMulticolumnView.tsx b/src/client/views/collections/CollectionMulticolumnView.tsx
new file mode 100644
index 000000000..955e87f13
--- /dev/null
+++ b/src/client/views/collections/CollectionMulticolumnView.tsx
@@ -0,0 +1,298 @@
+import { observer } from 'mobx-react';
+import { makeInterface } from '../../../new_fields/Schema';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { CollectionSubView } from './CollectionSubView';
+import * as React from "react";
+import { Doc } from '../../../new_fields/Doc';
+import { NumCast, StrCast, BoolCast } from '../../../new_fields/Types';
+import { ContentFittingDocumentView } from './../nodes/ContentFittingDocumentView';
+import { Utils } from '../../../Utils';
+import "./collectionMulticolumnView.scss";
+import { computed, trace } from 'mobx';
+import { Transform } from '../../util/Transform';
+
+type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
+const MulticolumnDocument = makeInterface(documentSchema);
+
+interface Unresolved {
+ target: Doc;
+ magnitude: number;
+ unit: string;
+}
+
+interface Resolved {
+ target: Doc;
+ pixels: number;
+}
+
+interface LayoutData {
+ unresolved: Unresolved[];
+ numFixed: number;
+ numRatio: number;
+ starSum: number;
+}
+
+const resolvedUnits = ["*", "px"];
+const resizerWidth = 2;
+const resizerOpacity = 0.4;
+
+@observer
+export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) {
+
+ @computed
+ private get ratioDefinedDocs() {
+ return this.childLayoutPairs.map(({ layout }) => layout).filter(({ widthUnit }) => StrCast(widthUnit) === "*");
+ }
+
+ @computed
+ private get resolvedLayoutInformation(): LayoutData {
+ const unresolved: Unresolved[] = [];
+ let starSum = 0, numFixed = 0, numRatio = 0;
+
+ for (const { layout } of this.childLayoutPairs) {
+ const unit = StrCast(layout.widthUnit);
+ const magnitude = NumCast(layout.widthMagnitude);
+ if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
+ if (unit === "*") {
+ starSum += magnitude;
+ numRatio++;
+ } else {
+ numFixed++;
+ }
+ unresolved.push({ target: layout, magnitude, unit });
+ }
+ // otherwise, the particular configuration entry is ignored and the remaining
+ // space is allocated as if the document were absent from the configuration list
+ }
+
+ setTimeout(() => {
+ const minimum = Math.min(...this.ratioDefinedDocs.map(({ widthMagnitude }) => NumCast(widthMagnitude)));
+ this.ratioDefinedDocs.forEach(layout => layout.widthMagnitude = NumCast(layout.widthMagnitude) / minimum);
+ });
+
+ return { unresolved, numRatio, numFixed, starSum };
+ }
+
+ /**
+ * This returns the total quantity, in pixels, that this
+ * view needs to reserve for child documents that have
+ * (with higher priority) requested a fixed pixel width.
+ *
+ * If the underlying resolvedLayoutInformation returns null
+ * because we're waiting on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get totalFixedAllocation(): number | undefined {
+ return this.resolvedLayoutInformation?.unresolved.reduce(
+ (sum, { magnitude, unit }) => sum + (unit === "px" ? magnitude : 0), 0);
+ }
+
+ /**
+ * This returns the total quantity, in pixels, that this
+ * view needs to reserve for child documents that have
+ * (with lower priority) requested a certain relative proportion of the
+ * remaining pixel width not allocated for fixed widths.
+ *
+ * If the underlying totalFixedAllocation returns undefined
+ * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get totalRatioAllocation(): number | undefined {
+ const layoutInfoLen = this.resolvedLayoutInformation?.unresolved.length;
+ if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
+ return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1));
+ }
+ }
+
+ /**
+ * This returns the total quantity, in pixels, that
+ * 1* (relative / star unit) is worth. For example,
+ * if the configuration has three documents, with, respectively,
+ * widths of 2*, 2* and 1*, and the panel width returns 1000px,
+ * this accessor returns 1000 / (2 + 2 + 1), or 200px.
+ * Elsewhere, this is then multiplied by each relative-width
+ * document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px).
+ *
+ * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
+ * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get columnUnitLength(): number | undefined {
+ if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
+ return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
+ }
+ }
+
+ private getColumnUnitLength = () => this.columnUnitLength;
+
+ private lookupPixels = (layout: Doc): number => {
+ const columnUnitLength = this.columnUnitLength;
+ if (columnUnitLength === undefined) {
+ return 0; // we're still waiting on promises to resolve
+ }
+ let width = NumCast(layout.widthMagnitude);
+ if (StrCast(layout.widthUnit) === "*") {
+ width *= columnUnitLength;
+ }
+ return width;
+ }
+
+ private lookupIndividualTransform = (layout: Doc) => {
+ const columnUnitLength = this.columnUnitLength;
+ if (columnUnitLength === undefined) {
+ return Transform.Identity(); // we're still waiting on promises to resolve
+ }
+ let offset = 0;
+ for (const { layout: candidate } of this.childLayoutPairs) {
+ if (candidate === layout) {
+ const shift = offset;
+ return this.props.ScreenToLocalTransform().translate(-shift, 0);
+ }
+ offset += this.lookupPixels(candidate) + resizerWidth;
+ }
+ return Transform.Identity(); // type coersion, this case should never be hit
+ }
+
+ @computed
+ private get contents(): JSX.Element[] | null {
+ trace();
+ const { childLayoutPairs } = this;
+ const { Document, PanelHeight } = this.props;
+ const collector: JSX.Element[] = [];
+ for (let i = 0; i < childLayoutPairs.length; i++) {
+ const { layout } = childLayoutPairs[i];
+ collector.push(
+ <div className={"document-wrapper"}>
+ <ContentFittingDocumentView
+ {...this.props}
+ key={Utils.GenerateGuid()}
+ Document={layout}
+ DataDocument={layout.resolvedDataDoc as Doc}
+ PanelWidth={() => this.lookupPixels(layout)}
+ PanelHeight={() => PanelHeight() - (BoolCast(Document.showWidthLabels) ? 20 : 0)}
+ getTransform={() => this.lookupIndividualTransform(layout)}
+ />
+ <WidthLabel
+ layout={layout}
+ collectionDoc={Document}
+ />
+ </div>,
+ <ResizeBar
+ width={resizerWidth}
+ key={Utils.GenerateGuid()}
+ columnUnitLength={this.getColumnUnitLength}
+ toLeft={layout}
+ toRight={childLayoutPairs[i + 1]?.layout}
+ />
+ );
+ }
+ collector.pop(); // removes the final extraneous resize bar
+ return collector;
+ }
+
+ render(): JSX.Element {
+ return (
+ <div
+ className={"collectionMulticolumnView_contents"}
+ ref={this.createDropTarget}
+ >
+ {this.contents}
+ </div>
+ );
+ }
+
+}
+
+interface SpacerProps {
+ width: number;
+ columnUnitLength(): number | undefined;
+ toLeft?: Doc;
+ toRight?: Doc;
+}
+
+interface WidthLabelProps {
+ layout: Doc;
+ collectionDoc: Doc;
+ decimals?: number;
+}
+
+@observer
+class WidthLabel extends React.Component<WidthLabelProps> {
+
+ @computed
+ private get contents() {
+ const { layout, decimals } = this.props;
+ const magnitude = NumCast(layout.widthMagnitude).toFixed(decimals ?? 3);
+ const unit = StrCast(layout.widthUnit);
+ return <span className={"display"}>{magnitude} {unit}</span>;
+ }
+
+ render() {
+ return BoolCast(this.props.collectionDoc.showWidthLabels) ? this.contents : (null);
+ }
+
+}
+
+@observer
+class ResizeBar extends React.Component<SpacerProps> {
+
+ private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
+ e.stopPropagation();
+ e.preventDefault();
+ window.removeEventListener("pointermove", this.onPointerMove);
+ window.removeEventListener("pointerup", this.onPointerUp);
+ window.addEventListener("pointermove", this.onPointerMove);
+ window.addEventListener("pointerup", this.onPointerUp);
+ }
+
+ private onPointerMove = ({ movementX }: PointerEvent) => {
+ const { toLeft, toRight, columnUnitLength } = this.props;
+ const target = movementX > 0 ? toRight : toLeft;
+ let scale = columnUnitLength();
+ if (target && scale) {
+ const { widthUnit, widthMagnitude } = target;
+ scale = widthUnit === "*" ? scale : 1;
+ target.widthMagnitude = NumCast(widthMagnitude) - Math.abs(movementX) / scale;
+ }
+ }
+
+ private get isActivated() {
+ const { toLeft, toRight } = this.props;
+ if (toLeft && toRight) {
+ if (StrCast(toLeft.widthUnit) === "px" && StrCast(toRight.widthUnit) === "px") {
+ return false;
+ }
+ return true;
+ } else if (toLeft) {
+ if (StrCast(toLeft.widthUnit) === "px") {
+ return false;
+ }
+ return true;
+ } else if (toRight) {
+ if (StrCast(toRight.widthUnit) === "px") {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private onPointerUp = () => {
+ window.removeEventListener("pointermove", this.onPointerMove);
+ window.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ render() {
+ return (
+ <div
+ className={"resizer"}
+ style={{
+ width: this.props.width,
+ opacity: this.isActivated ? resizerOpacity : 0
+ }}
+ onPointerDown={this.registerResizing}
+ />
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPivotView.scss b/src/client/views/collections/CollectionPivotView.scss
new file mode 100644
index 000000000..bd3d6c77b
--- /dev/null
+++ b/src/client/views/collections/CollectionPivotView.scss
@@ -0,0 +1,57 @@
+.collectionPivotView {
+ display: flex;
+ flex-direction: row;
+ position: absolute;
+ height:100%;
+ width:100%;
+ .collectionPivotView-flyout {
+ width: 400px;
+ height: 300px;
+ display: inline-block;
+ .collectionPivotView-flyout-item {
+ background-color: lightgray;
+ text-align: left;
+ display: inline-block;
+ position: relative;
+ width: 100%;
+ }
+ }
+
+ .collectionPivotView-treeView {
+ display:flex;
+ flex-direction: column;
+ width: 200px;
+ height: 100%;
+ .collectionPivotView-addfacet {
+ display:inline-block;
+ width: 200px;
+ height: 30px;
+ background: darkGray;
+ text-align: center;
+ .collectionPivotView-button {
+ align-items: center;
+ display: flex;
+ width: 100%;
+ height: 100%;
+ .collectionPivotView-span {
+ margin: auto;
+ }
+ }
+ > div, > div > div {
+ width: 100%;
+ height: 100%;
+ text-align: center;
+ }
+ }
+ .collectionPivotView-tree {
+ display:inline-block;
+ width: 200px;
+ height: calc(100% - 30px);
+ }
+ }
+ .collectionPivotView-pivot {
+ display:inline-block;
+ width: calc(100% - 200px);
+ height: 100%;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
new file mode 100644
index 000000000..6af7cce70
--- /dev/null
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -0,0 +1,118 @@
+import { CollectionSubView } from "./CollectionSubView";
+import React = require("react");
+import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx";
+import { faEdit, faChevronCircleUp } from "@fortawesome/free-solid-svg-icons";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import "./CollectionPivotView.scss";
+import { observer } from "mobx-react";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { CollectionTreeView } from "./CollectionTreeView";
+import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
+import { Docs } from "../../documents/Documents";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { CompileScript } from "../../util/Scripting";
+import { anchorPoints, Flyout } from "../TemplateMenu";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { List } from "../../../new_fields/List";
+import { Set } from "typescript-collections";
+
+@observer
+export class CollectionPivotView extends CollectionSubView(doc => doc) {
+ componentDidMount = () => {
+ this.props.Document.freeformLayoutEngine = "pivot";
+ if (!this.props.Document.facetCollection) {
+ const facetCollection = Docs.Create.FreeformDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true });
+ facetCollection.target = this.props.Document;
+
+ const scriptText = "setDocFilter(context.target, heading, this.title, checked)";
+ const script = CompileScript(scriptText, {
+ params: { this: Doc.name, heading: "boolean", checked: "boolean", context: Doc.name },
+ typecheck: false,
+ editable: true,
+ });
+ if (script.compiled) {
+ facetCollection.onCheckedClick = new ScriptField(script);
+ }
+
+ const openDocText = "const alias = getAlias(this); alias.layoutKey = 'layoutCustom'; useRightSplit(alias); ";
+ const openDocScript = CompileScript(openDocText, {
+ params: { this: Doc.name, heading: "boolean", checked: "boolean", context: Doc.name },
+ typecheck: false,
+ editable: true,
+ });
+ if (openDocScript.compiled) {
+ this.props.Document.onChildClick = new ScriptField(openDocScript);
+ }
+
+ this.props.Document.facetCollection = facetCollection;
+ this.props.Document.fitToBox = true;
+ }
+ }
+
+ @computed get fieldExtensionDoc() {
+ return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
+ }
+
+ bodyPanelWidth = () => this.props.PanelWidth() - 200;
+ getTransform = () => this.props.ScreenToLocalTransform().translate(-200, 0);
+
+ @computed get _allFacets() {
+ const facets = new Set<string>();
+ this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
+ return facets.toArray();
+ }
+
+ facetClick = (facet: string) => {
+ const facetCollection = this.props.Document.facetCollection;
+ if (facetCollection instanceof Doc) {
+ const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facet);
+ if (found !== -1) {
+ //Doc.RemoveDocFromList(facetCollection, "data", DocListCast(facetCollection.data)[found]);
+ (facetCollection.data as List<Doc>).splice(found, 1);
+ } else {
+ const facetValues = new Set<string>();
+ this.childDocs.forEach(child => {
+ Object.keys(Doc.GetProto(child)).forEach(key => child[key] instanceof Doc && facetValues.add((child[key] as Doc)[facet]?.toString() || "(null)"));
+ facetValues.add(child[facet]?.toString() || "(null)");
+ });
+
+ const newFacetVals = facetValues.toArray().map(val => Docs.Create.TextDocument({ title: val.toString() }));
+ const newFacet = Docs.Create.FreeformDocument(newFacetVals, { title: facet, treeViewOpen: true, isFacetFilter: true });
+ Doc.AddDocToList(facetCollection, "data", newFacet);
+ }
+ }
+ }
+
+ render() {
+ const facetCollection = Cast(this.props.Document?.facetCollection, Doc, null);
+ const flyout = (
+ <div className="collectionPivotView-flyout" title=" ">
+ {this._allFacets.map(facet => <label className="collectionPivotView-flyout-item" onClick={e => this.facetClick(facet)}>
+ <input type="checkbox" checked={this.props.Document.facetCollection instanceof Doc && DocListCast(this.props.Document.facetCollection.data).some(d => {
+ return d.title === facet;
+ })} />
+ <span className="checkmark" />
+ {facet}
+ </label>)}
+ </div>
+ );
+ return !facetCollection ? (null) : <div className="collectionPivotView">
+ <div className="collectionPivotView-treeView">
+ <div className="collectionPivotView-addFacet" onPointerDown={e => e.stopPropagation()}>
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
+ <div className="collectionPivotView-button">
+ <span className="collectionPivotView-span">Facet Filters</span>
+ <FontAwesomeIcon icon={faEdit} size={"lg"} />
+ </div>
+ </Flyout>
+ </div>
+ <div className="collectionPivotView-tree">
+ <CollectionTreeView {...this.props} Document={facetCollection} />
+ </div>
+ </div>
+ <div className="collectionPivotView-pivot">
+ <CollectionFreeFormView {...this.props} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
+ </div>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index 0114342b9..92dc8780e 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -286,7 +286,6 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
}
@undoBatch
- @action
onKeyDown = (e: React.KeyboardEvent): void => {
if (e.key === "Enter") {
const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
@@ -296,7 +295,7 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) {
this.onSelect(this._searchTerm);
} else {
- this._searchTerm = this._key;
+ this.setSearchTerm(this._key);
}
}
}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index bb706e528..b466d9511 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -157,8 +157,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
pinToPres={this.props.pinToPres}
- setPreviewScript={this.setPreviewScript}
- previewScript={this.previewScript}
/>
</div>;
}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index b8423af20..7fe42386a 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -22,7 +22,6 @@ import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewField
import { CollectionSubView } from "./CollectionSubView";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
-import { ScriptBox } from "../ScriptBox";
import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
import { TraceMobx } from "../../../new_fields/util";
import { CollectionViewType } from "./CollectionView";
@@ -40,9 +39,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@observable _scroll = 0; // used to force the document decoration to update when scrolling
@computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
@computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
- @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
+ @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized).map(d => (Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d).layout as Doc) || d); }
@computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
- @computed get yMargin() { return Math.max(this.props.Document.showTitle ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); }
+ @computed get yMargin() { return Math.max(this.props.Document.showTitle && !this.props.Document.showTitleHover ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); }
@computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
@computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); }
@computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
@@ -53,22 +52,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
@computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
- childDocHeight(child: Doc) { return this.getDocHeight(Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, child).layout); }
-
children(docs: Doc[]) {
this._docXfs.length = 0;
return docs.map((d, i) => {
- const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d);
- const layoutDoc = pair.layout ? Doc.Layout(pair.layout) : d;
- const width = () => Math.min(layoutDoc.nativeWidth && !layoutDoc.ignoreAspect && !this.props.Document.fillColumn ? layoutDoc[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
- const height = () => this.getDocHeight(layoutDoc);
+ const width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
+ const height = () => this.getDocHeight(d);
const dref = React.createRef<HTMLDivElement>();
- const dxf = () => this.getDocTransform(layoutDoc, dref.current!);
+ const dxf = () => this.getDocTransform(d, dref.current!);
this._docXfs.push({ dxf: dxf, width: width, height: height });
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
const style = this.isStackingView ? { width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` };
return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
- {this.getDisplayDoc(pair.layout || d, pair.data, dxf, width)}
+ {this.getDisplayDoc(d, (d.resolvedDataDoc as Doc) || d, dxf, width)}
</div>;
});
}
@@ -115,7 +110,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => {
const r1 = Math.max(maxHght,
(this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => {
- const val = height + this.childDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
+ const val = height + this.getDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
return val;
}, this.yMargin));
return r1;
@@ -157,7 +152,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
overlays = (doc: Doc) => {
- return doc.type === DocumentType.IMG || doc.type === DocumentType.VID ? { title: StrCast(this.props.Document.showTitles), caption: StrCast(this.props.Document.showCaptions) } : {};
+ return doc.type === DocumentType.IMG || doc.type === DocumentType.VID ? { title: StrCast(this.props.Document.showTitles), titleHover: StrCast(this.props.Document.showTitleHovers), caption: StrCast(this.props.Document.showCaptions) } : {};
}
@computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
@@ -187,9 +182,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
active={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- setPreviewScript={emptyFunction}
- previewScript={undefined}>
+ pinToPres={this.props.pinToPres}>
</ContentFittingDocumentView>;
}
getDocHeight(d?: Doc) {
@@ -294,7 +287,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.sectionFilter;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
- const types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
+ const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
type = types[0];
}
@@ -319,15 +312,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const y = this._scroll; // required for document decorations to update when the text box container is scrolled
const { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
const outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
+ const scaling = 1 / Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]());
const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
- return this.props.ScreenToLocalTransform().
- translate(offset[0], offset[1] + (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0));
+ const offsetx = (doc[WidthSym]() - doc[WidthSym]() / scaling) / 2;
+ const offsety = (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0);
+ return this.props.ScreenToLocalTransform().translate(offset[0] - offsetx, offset[1] + offsety).scale(scaling);
}
sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.sectionFilter;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
- const types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
+ const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
type = types[0];
}
@@ -376,11 +371,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
subItems.push({ description: `${this.props.Document.showTitles ? "Hide Titles" : "Show Titles"}`, event: () => this.props.Document.showTitles = !this.props.Document.showTitles ? "title" : "", icon: "plus" });
subItems.push({ description: `${this.props.Document.showCaptions ? "Hide Captions" : "Show Captions"}`, event: () => this.props.Document.showCaptions = !this.props.Document.showCaptions ? "caption" : "", icon: "plus" });
ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" });
-
- const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
- const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
- onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) });
- !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
}
@@ -404,6 +394,11 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
<div className="collectionStackingMasonry-cont" >
<div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
ref={this.createRef}
+ style={{
+ transform: `scale(${Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]())})`,
+ height: `${Math.max(100, 100 * 1 / Math.min(this.props.PanelWidth() / this.layoutDoc[WidthSym](), this.props.PanelHeight() / this.layoutDoc[HeightSym]()))}%`,
+ transformOrigin: "top"
+ }}
onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
onDrop={this.onDrop.bind(this)}
onContextMenu={this.onContextMenu}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 4133956fc..5f4ee3669 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -93,8 +93,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
return this.props.annotationsKey ? (this.extensionDoc ? this.extensionDoc[this.props.annotationsKey] : undefined) : this.dataDoc[this.props.fieldKey];
}
- get childLayoutPairs() {
- return this.childDocs.map(cd => Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, cd)).filter(pair => pair.layout).map(pair => ({ layout: pair.layout!, data: pair.data! }));
+ get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
+ const { Document, DataDoc, fieldKey } = this.props;
+ const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, fieldKey, doc)).filter(pair => pair.layout);
+ return validPairs.map(({ data, layout }) => ({ data: data!, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
get childDocList() {
return Cast(this.dataField, listSpec(Doc));
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 2b13d87ee..70860b6bd 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,7 +8,7 @@ import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
import { Document, listSpec } from '../../../new_fields/Schema';
import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
+import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
import { emptyFunction, Utils, returnFalse, emptyPath } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
@@ -28,6 +28,7 @@ import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import React = require("react");
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { ScriptBox } from '../ScriptBox';
export interface TreeViewProps {
@@ -51,12 +52,13 @@ export interface TreeViewProps {
outdentDocument?: () => void;
ScreenToLocalTransform: () => Transform;
outerXf: () => { translateX: number, translateY: number };
- treeViewId: string;
+ treeViewId: Doc;
parentKey: string;
active: (outsideReaction?: boolean) => boolean;
hideHeaderFields: () => boolean;
preventTreeViewOpen: boolean;
renderedIds: string[];
+ onCheckedClick?: ScriptField;
}
library.add(faTrashAlt);
@@ -232,8 +234,8 @@ class TreeView extends React.Component<TreeViewProps> {
if (inside) {
addDoc = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) || addDoc(doc);
}
- const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments);
- return ((de.complete.docDragData.dropAction && (de.complete.docDragData.treeViewId !== this.props.treeViewId)) || de.complete.docDragData.userDropAction) ?
+ const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId[Id] ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments);
+ return ((de.complete.docDragData.dropAction && (de.complete.docDragData.treeViewId !== this.props.treeViewId[Id])) || de.complete.docDragData.userDropAction) ?
de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d) || added, false)
: de.complete.docDragData.moveDocument ?
movedDocs.reduce((added, d) => de.complete.docDragData?.moveDocument?.(d, undefined, addDoc) || added, false)
@@ -286,7 +288,7 @@ class TreeView extends React.Component<TreeViewProps> {
DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.hideHeaderFields, this.props.preventTreeViewOpen,
- [...this.props.renderedIds, doc[Id]], this.props.libraryPath);
+ [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick);
} else {
contentElement = <EditableView
key="editableView"
@@ -331,7 +333,7 @@ class TreeView extends React.Component<TreeViewProps> {
this.templateDataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.hideHeaderFields, this.props.preventTreeViewOpen,
- [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath)}
+ [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick)}
</ul >;
} else if (this.treeViewExpandedView === "fields") {
return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
@@ -359,16 +361,32 @@ class TreeView extends React.Component<TreeViewProps> {
active={this.props.active}
whenActiveChanged={emptyFunction}
addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- setPreviewScript={emptyFunction} />
+ pinToPres={this.props.pinToPres} />
</div>;
}
}
+ @action
+ bulletClick = (e: React.MouseEvent) => {
+ if (this.props.onCheckedClick && this.props.document.type !== DocumentType.COL) {
+ this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check";
+ ScriptCast(this.props.onCheckedClick).script.run({
+ this: this.props.document.isTemplateField && this.props.dataDoc ? this.props.dataDoc : this.props.document,
+ heading: this.props.containingCollection.title,
+ checked: this.props.document.treeViewChecked === "check" ? false : this.props.document.treeViewChecked === "x" ? "x" : "none",
+ context: this.props.treeViewId
+ }, console.log);
+ } else {
+ this.treeViewOpen = !this.treeViewOpen;
+ }
+ e.stopPropagation();
+ }
+
@computed
get renderBullet() {
- return <div className="bullet" title="view inline" onClick={action((e: React.MouseEvent) => { this.treeViewOpen = !this.treeViewOpen; e.stopPropagation(); })} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
- {<FontAwesomeIcon icon={!this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
+ const checked = this.props.document.type === DocumentType.COL ? undefined : this.props.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined;
+ return <div className="bullet" title="view inline" onClick={this.bulletClick} style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "black"), opacity: 0.4 }}>
+ {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />}
</div>;
}
/**
@@ -377,7 +395,7 @@ class TreeView extends React.Component<TreeViewProps> {
@computed
get renderTitle() {
const reference = React.createRef<HTMLDivElement>();
- const onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId, true);
+ const onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true);
const headerElements = (
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
@@ -427,7 +445,7 @@ class TreeView extends React.Component<TreeViewProps> {
}
public static GetChildElements(
childDocs: Doc[],
- treeViewId: string,
+ treeViewId: Doc,
containingCollection: Doc,
dataDoc: Doc | undefined,
key: string,
@@ -448,7 +466,8 @@ class TreeView extends React.Component<TreeViewProps> {
hideHeaderFields: () => boolean,
preventTreeViewOpen: boolean,
renderedIds: string[],
- libraryPath: Doc[] | undefined
+ libraryPath: Doc[] | undefined,
+ onCheckedClick: ScriptField | undefined
) {
const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
@@ -540,6 +559,7 @@ class TreeView extends React.Component<TreeViewProps> {
key={child[Id]}
indentDocument={indent}
outdentDocument={outdent}
+ onCheckedClick={onCheckedClick}
renderDepth={renderDepth}
deleteDoc={remove}
addDocument={addDocument}
@@ -608,6 +628,10 @@ export class CollectionTreeView extends CollectionSubView(Document) {
layoutItems.push({ description: (this.props.Document.hideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.hideHeaderFields = !this.props.Document.hideHeaderFields, icon: "paint-brush" });
ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
}
+ const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+ const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+ onClicks.push({ description: "Edit onChecked Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Checked Changed ...", this.props.Document, "onCheckedClick", obj.x, obj.y, { heading: "boolean", checked: "boolean" }) });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});
@@ -632,7 +656,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
onDrop={this.onTreeDrop}
ref={this.createTreeDropTarget}>
- <EditableView
+ {(this.props.Document.treeViewHideTitle ? (null) : <EditableView
contents={this.dataDoc.title}
display={"block"}
maxHeight={72}
@@ -645,14 +669,14 @@ export class CollectionTreeView extends CollectionSubView(Document) {
const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
TreeView.loadId = doc[Id];
Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, false);
- })} />
+ })} />)}
{this.props.Document.allowClear ? this.renderClearButton : (null)}
<ul className="no-indent" style={{ width: "max-content" }} >
{
- TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
+ TreeView.GetChildElements(this.childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.hideHeaderFields),
- BoolCast(this.props.Document.preventTreeViewOpen), [], this.props.LibraryPath)
+ BoolCast(this.props.Document.preventTreeViewOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick))
}
</ul>
</div >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 88023783b..4bd456233 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -4,36 +4,38 @@ import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagra
import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from "mobx-react";
import * as React from 'react';
+import Lightbox from 'react-image-lightbox-with-rotate';
+import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
+import { DateField } from '../../../new_fields/DateField';
+import { Doc, DocListCast } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
-import { StrCast, BoolCast, Cast } from '../../../new_fields/Types';
+import { listSpec } from '../../../new_fields/Schema';
+import { BoolCast, Cast, StrCast } from '../../../new_fields/Types';
+import { ImageField } from '../../../new_fields/URLField';
+import { TraceMobx } from '../../../new_fields/util';
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { Utils } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
+import { ImageUtils } from '../../util/Import & Export/ImageUtils';
+import { SelectionManager } from '../../util/SelectionManager';
import { ContextMenu } from "../ContextMenu";
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { ScriptBox } from '../ScriptBox';
+import { Touchable } from '../Touchable';
import { CollectionDockingView } from "./CollectionDockingView";
import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionLinearView } from './CollectionLinearView';
+import { CollectionMulticolumnView } from './CollectionMulticolumnView';
+import { CollectionPivotView } from './CollectionPivotView';
import { CollectionSchemaView } from "./CollectionSchemaView";
import { CollectionStackingView } from './CollectionStackingView';
+import { CollectionStaffView } from './CollectionStaffView';
import { CollectionTreeView } from "./CollectionTreeView";
+import './CollectionView.scss';
import { CollectionViewBaseChrome } from './CollectionViewChromes';
-import { ImageUtils } from '../../util/Import & Export/ImageUtils';
-import { CollectionLinearView } from '../CollectionLinearView';
-import { CollectionStaffView } from './CollectionStaffView';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { ImageField } from '../../../new_fields/URLField';
-import { DocListCast } from '../../../new_fields/Doc';
-import Lightbox from 'react-image-lightbox-with-rotate';
-import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
export const COLLECTION_BORDER_WIDTH = 2;
-import { DateField } from '../../../new_fields/DateField';
-import { Doc, } from '../../../new_fields/Doc';
-import { listSpec } from '../../../new_fields/Schema';
-import { DocumentManager } from '../../util/DocumentManager';
-import { SelectionManager } from '../../util/SelectionManager';
-import './CollectionView.scss';
-import { FieldViewProps, FieldView } from '../nodes/FieldView';
-import { Touchable } from '../Touchable';
-import { TraceMobx } from '../../../new_fields/util';
-import { Utils } from '../../../Utils';
const path = require('path');
library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -47,7 +49,8 @@ export enum CollectionViewType {
Masonry,
Pivot,
Linear,
- Staff
+ Staff,
+ Multicolumn
}
export namespace CollectionViewType {
@@ -60,7 +63,8 @@ export namespace CollectionViewType {
["stacking", CollectionViewType.Stacking],
["masonry", CollectionViewType.Masonry],
["pivot", CollectionViewType.Pivot],
- ["linear", CollectionViewType.Linear]
+ ["linear", CollectionViewType.Linear],
+ ["multicolumn", CollectionViewType.Multicolumn]
]);
export const valueOf = (value: string) => stringMapping.get(value.toLowerCase());
@@ -172,10 +176,11 @@ export class CollectionView extends Touchable<FieldViewProps> {
case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />);
case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} />);
case CollectionViewType.Staff: return (<CollectionStaffView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
+ case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
- case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (<CollectionFreeFormView key="collview" {...props} />); }
+ case CollectionViewType.Pivot: { return (<CollectionPivotView key="collview" {...props} />); }
case CollectionViewType.Freeform:
default: { this.props.Document.freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
}
@@ -213,6 +218,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
}, icon: "ellipsis-v"
});
subItems.push({ description: "Staff", event: () => this.props.Document.viewType = CollectionViewType.Staff, icon: "music" });
+ subItems.push({ description: "Multicolumn", event: () => this.props.Document.viewType = CollectionViewType.Multicolumn, icon: "columns" });
subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" });
subItems.push({ description: "Pivot", event: () => this.props.Document.viewType = CollectionViewType.Pivot, icon: "columns" });
switch (this.props.Document.viewType) {
@@ -233,6 +239,11 @@ export class CollectionView extends Touchable<FieldViewProps> {
const moreItems = more && "subitems" in more ? more.subitems : [];
moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
!more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+
+ const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+ const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+ onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
}
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index a870b6043..996c7671e 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -217,7 +217,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
`(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
"true";
- this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });
+ const docFilter = Cast(this.props.CollectionView.props.Document.docFilter, listSpec("string"), []);
+ const docFilterText = Doc.MakeDocFilter(docFilter);
+ const finalScript = docFilterText && !fullScript.startsWith("(())") ? `${fullScript} ${docFilterText ? "&&" : ""} (${docFilterText})` :
+ docFilterText ? docFilterText : fullScript;
+
+ this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(finalScript, { doc: Doc.name });
}
@action
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 012115b1f..8c8da63cc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -44,7 +44,7 @@ function toLabel(target: FieldResult<Field>) {
return String(target);
}
-export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
+export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200);
const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
@@ -57,9 +57,14 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
}
const minSize = Array.from(pivotColumnGroups.entries()).reduce((min, pair) => Math.min(min, pair[1].length), Infinity);
- const numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize)));
+ let numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize)));
const docMap = new Map<Doc, ViewDefBounds>();
const groupNames: PivotData[] = [];
+ if (panelDim[0] < 2500) numCols = Math.min(5, numCols);
+ if (panelDim[0] < 2000) numCols = Math.min(4, numCols);
+ if (panelDim[0] < 1400) numCols = Math.min(3, numCols);
+ if (panelDim[0] < 1000) numCols = Math.min(2, numCols);
+ if (panelDim[0] < 600) numCols = 1;
const expander = 1.05;
const gap = .15;
@@ -73,7 +78,7 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
x,
y: pivotAxisWidth + 50,
width: pivotAxisWidth * expander * numCols,
- height: 100,
+ height: NumCast(pivotDoc.pivotFontSize, 10),
fontSize: NumCast(pivotDoc.pivotFontSize, 10)
});
for (const doc of val) {
@@ -85,14 +90,14 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
wid = layoutDoc.nativeHeight ? (NumCast(layoutDoc.nativeWidth) / NumCast(layoutDoc.nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
}
docMap.set(doc, {
- x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2,
+ x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.length < numCols ? (numCols - val.length) * pivotAxisWidth / 2 : 0),
y: -y,
width: wid,
height: hgt
});
xCount++;
if (xCount >= numCols) {
- xCount = (pivotAxisWidth - wid) / 2;
+ xCount = 0;
y += pivotAxisWidth * expander;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 178a5bcdc..b8fbaef5c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -54,8 +54,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
} else {
setTimeout(() => {
(this.props.A.props.Document[(this.props.A.props as any).fieldKey] as Doc);
- let m = targetBhyperlink.getBoundingClientRect();
- let mp = this.props.A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+ const m = targetBhyperlink.getBoundingClientRect();
+ const mp = this.props.A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
this.props.A.props.Document[afield + "_x"] = mp[0] / this.props.A.props.PanelWidth() * 100;
this.props.A.props.Document[afield + "_y"] = mp[1] / this.props.A.props.PanelHeight() * 100;
}, 0);
@@ -66,8 +66,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
} else {
setTimeout(() => {
(this.props.B.props.Document[(this.props.B.props as any).fieldKey] as Doc);
- let m = targetAhyperlink.getBoundingClientRect();
- let mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+ const m = targetAhyperlink.getBoundingClientRect();
+ const mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
this.props.B.props.Document[afield + "_x"] = mp[0] / this.props.B.props.PanelWidth() * 100;
this.props.B.props.Document[afield + "_y"] = mp[1] / this.props.B.props.PanelHeight() * 100;
}, 0);
@@ -93,8 +93,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
apt.point.x, apt.point.y);
const pt1 = [apt.point.x, apt.point.y];
const pt2 = [bpt.point.x, bpt.point.y];
- let aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
- let bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
+ const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
+ const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
return !aActive && !bActive ? (null) :
<line key="linkLine" className="collectionfreeformlinkview-linkLine"
style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b302bb1dc..b5e0ce9f0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -9,7 +9,7 @@ import { Id } from "../../../../new_fields/FieldSymbols";
import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, DateCast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types";
+import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../new_fields/Types";
import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
@@ -57,6 +57,8 @@ export const panZoomSchema = createSchema({
useClusters: "boolean",
isRuleProvider: "boolean",
fitToBox: "boolean",
+ xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
+ yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
panTransformType: "string",
scrollHeight: "number",
fitX: "number",
@@ -83,7 +85,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
@computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
- @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); }
+ @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); }
@computed get nativeWidth() { return this.Document.fitToContent ? 0 : this.Document.nativeWidth || 0; }
@computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; }
private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
@@ -684,6 +686,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
getScale = () => this.Document.scale || 1;
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
+ @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
@@ -693,7 +696,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
LibraryPath: this.libraryPath,
layoutKey: undefined,
ruleProvider: this.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, //bcz: hack! - currently ruleProviders apply to documents in nested colleciton, not direct children of themselves
- onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
+ //onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
+ onClick: this.onChildClickHandler,
ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
renderDepth: this.props.renderDepth + 1,
PanelWidth: childLayout[WidthSym],
@@ -746,7 +750,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
doPivotLayout(poolData: ObservableMap<string, any>) {
return computePivotLayout(poolData, this.props.Document, this.childDocs,
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), this.viewDefsToJSX);
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
}
doFreeformLayout(poolData: ObservableMap<string, any>) {
@@ -774,9 +778,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
this.childLayoutPairs.filter((pair, i) => this.isCurrent(pair.layout)).forEach(pair =>
computedElementData.elements.push({
- ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.childDataProvider}
+ ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} {...this.getChildDocumentViewProps(pair.layout, pair.data)}
+ dataProvider={this.childDataProvider}
ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
- jitterRotation={NumCast(this.props.Document.jitterRotation)} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
+ jitterRotation={NumCast(this.props.Document.jitterRotation)}
+ fitToBox={this.props.fitToBox || this.Document.freeformLayoutEngine === "pivot"} />,
bounds: this.childDataProvider(pair.layout)
}));
@@ -983,6 +989,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
</CollectionFreeFormViewPannableContents>
</MarqueeView>;
}
+ @computed get contentScaling() {
+ const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
+ const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1;
+ return wscale < hscale ? wscale : hscale;
+ }
render() {
TraceMobx();
// update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
@@ -994,9 +1005,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
if (!this.extensionDoc) return (null);
// let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
- return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
- style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}>
+ return <div className={"collectionfreeformview-container"}
+ ref={this.createDashEventsTarget}
+ onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}
+ style={{
+ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
+ transformOrigin: this.contentScaling ? "left top" : "",
+ width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
+ height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+ }}>
{!this.Document.LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
this.placeholder : this.marqueeView}
<CollectionFreeFormOverlayView elements={this.elementFunc} />