aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Wilkins <abdullah_ahmed@brown.edu>2019-07-23 19:17:03 -0400
committerSam Wilkins <abdullah_ahmed@brown.edu>2019-07-23 19:17:03 -0400
commit6bf6c34a4c3643a2ee438e49e10267de15e431e7 (patch)
tree7ebe5d864cbedd07b8f73074ce5f8e140e8733e6
parent0ef6d9b5a3f90552562f4a6392967887d8805cc3 (diff)
parent0ebcadb80a89e7fe4f8f2a8a47570b19fd2f8711 (diff)
merge
-rw-r--r--src/client/documents/Documents.ts4
-rw-r--r--src/client/views/EditableView.scss10
-rw-r--r--src/client/views/EditableView.tsx30
-rw-r--r--src/client/views/MetadataEntryMenu.tsx1
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx2
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx3
-rw-r--r--src/client/views/collections/CollectionStackingView.scss136
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx188
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx250
-rw-r--r--src/client/views/collections/CollectionView.tsx34
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss54
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx152
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx3
-rw-r--r--src/client/views/nodes/DocumentView.tsx10
-rw-r--r--src/new_fields/SchemaHeaderField.ts46
15 files changed, 797 insertions, 126 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7563fda20..a419aed54 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -410,6 +410,10 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking });
}
+ export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) {
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Masonry });
+ }
+
export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
}
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
index a5150cd66..19512362e 100644
--- a/src/client/views/EditableView.scss
+++ b/src/client/views/EditableView.scss
@@ -1,20 +1,24 @@
-.editableView-container-editing, .editableView-container-editing-oneLine {
+.editableView-container-editing,
+.editableView-container-editing-oneLine {
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto;
overflow: hidden;
}
+
.editableView-container-editing-oneLine {
span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- display:block;
+ display: block;
}
+
input {
- display:block;
+ display: block;
}
}
+
.editableView-input {
width: 100%;
background: inherit;
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index c5886f552..5cbecf2c9 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -2,6 +2,7 @@ import React = require('react');
import { observer } from 'mobx-react';
import { observable, action, trace } from 'mobx';
import "./EditableView.scss";
+import * as Autosuggest from 'react-autosuggest';
export interface EditableProps {
/**
@@ -28,6 +29,13 @@ export interface EditableProps {
fontSize?: number;
height?: number;
display?: string;
+ autosuggestProps?: {
+ resetValue: () => void;
+ value: string,
+ onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void,
+ autosuggestProps: Autosuggest.AutosuggestProps<string>
+
+ };
oneLine?: boolean;
editing?: boolean;
onClick?: (e: React.MouseEvent) => boolean;
@@ -95,10 +103,26 @@ export class EditableView extends React.Component<EditableProps> {
render() {
if (this._editing) {
- return <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
- onBlur={action(() => {this._editing = false; this.props.isEditingCallback && this.props.isEditingCallback(false);})} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
- style={{ display: this.props.display, fontSize: this.props.fontSize }} />;
+ return this.props.autosuggestProps
+ ? <Autosuggest
+ {...this.props.autosuggestProps.autosuggestProps}
+ inputProps={{
+ className: "editableView-input",
+ onKeyDown: this.onKeyDown,
+ autoFocus: true,
+ onBlur: action(() => this._editing = false),
+ onPointerDown: this.stopPropagation,
+ onClick: this.stopPropagation,
+ onPointerUp: this.stopPropagation,
+ value: this.props.autosuggestProps.value,
+ onChange: this.props.autosuggestProps.onChange
+ }}
+ />
+ : <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
+ onBlur={action(() => {this._editing = false; this.props.isEditingCallback && this.props.isEditingCallback(false);})} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
+ style={{ display: this.props.display, fontSize: this.props.fontSize }} />;
} else {
+ if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue();
return (
<div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`}
style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index bd5a307b3..7fce3dd0c 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -74,6 +74,7 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
this.userModified = e.target.value.trim() !== "";
}
+ @action
onValueKeyDown = async (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
const script = KeyValueBox.CompileKVPScript(this._currentValue);
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 72faf52c4..4cdbd8554 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -32,7 +32,7 @@ export interface CollectionRenderProps {
export interface CollectionViewProps extends FieldViewProps {
onContextMenu?: (e: React.MouseEvent) => void;
- children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null;
+ children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null | (JSX.Element | null)[];
className?: string;
contentRef?: React.Ref<HTMLDivElement>;
}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 39844dea2..febf95dc7 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -31,17 +31,14 @@ import { CollectionVideoView } from "./CollectionVideoView";
import { CollectionView } from "./CollectionView";
import { undoBatch } from "../../util/UndoManager";
import { timesSeries } from "async";
-<<<<<<< HEAD
import { CollectionSchemaHeader, CollectionSchemaAddColumnHeader } from "./CollectionSchemaHeaders";
import { CellProps, CollectionSchemaCell, CollectionSchemaNumberCell, CollectionSchemaStringCell, CollectionSchemaBooleanCell, CollectionSchemaCheckboxCell, CollectionSchemaDocCell } from "./CollectionSchemaCells";
import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
import { SelectionManager } from "../../util/SelectionManager";
import { DocumentManager } from "../../util/DocumentManager";
-=======
import { ImageBox } from "../nodes/ImageBox";
import { ComputedField } from "../../../new_fields/ScriptField";
->>>>>>> 86971952237b8bd01a23b52db662740126bd8477
library.add(faCog);
library.add(faPlus);
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 7ebf5f77c..e0ced8af4 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -1,9 +1,13 @@
@import "../globalCssVariables";
+
.collectionStackingView {
height: 100%;
width: 100%;
position: absolute;
+ display: flex;
overflow-y: auto;
+ flex-wrap: wrap;
+
.collectionStackingView-docView-container {
width: 45%;
margin: 5% 2.5%;
@@ -18,21 +22,23 @@
align-items: center;
}
- .collectionStackingView-masonrySingle, .collectionStackingView-masonryGrid {
- width:100%;
- height:100%;
+ .collectionStackingView-masonrySingle,
+ .collectionStackingView-masonryGrid {
+ width: 100%;
+ height: 100%;
position: absolute;
- display:grid;
+ display: grid;
top: 0;
left: 0;
width: 100%;
position: absolute;
}
+
.collectionStackingView-masonrySingle {
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
position: absolute;
- display:flex;
+ display: flex;
flex-direction: column;
top: 0;
left: 0;
@@ -52,34 +58,120 @@
}
.collectionStackingView-columnDragger {
- width: 15;
- height: 15;
+ width: 15;
+ height: 15;
position: absolute;
margin-left: -5;
}
- .collectionStackingView-columnDoc{
+ .collectionStackingView-columnDoc {
display: inline-block;
}
- .collectionStackingView-columnDoc,
- .collectionStackingView-masonryDoc {
- margin-left: auto;
- margin-right: auto;
- }
-
.collectionStackingView-masonryDoc {
transform-origin: top left;
grid-column-end: span 1;
height: 100%;
}
- .collectionStackingView-sectionHeader {
- width: 90%;
- background: gray;
+
+ .collectionStackingView-sectionHeader {
text-align: center;
- margin-left: 5%;
- margin-right: 5%;
- color: white;
+ margin-left: 5px;
+ margin-right: 5px;
margin-top: 10px;
+ overflow: hidden;
+
+ .editableView-input {
+ color: black;
+ }
+
+ .editableView-input:hover,
+ .editableView-container-editing:hover,
+ .editableView-container-editing-oneLine:hover {
+ cursor: text
+ }
+
+ .collectionStackingView-sectionHeader-subCont {
+ outline: none;
+ border: 0px;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ position: relative;
+
+ .editableView-container-editing-oneLine,
+ .editableView-container-editing {
+ color: grey;
+ padding: 10px;
+ }
+
+ .editableView-input:hover,
+ .editableView-container-editing:hover,
+ .editableView-container-editing-oneLine:hover {
+ cursor: text
+ }
+
+ .editableView-input {
+ padding: 12px 10px 11px 10px;
+ border: 0px;
+ color: grey;
+ // font-size: 75%;
+ text-align: center;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ outline-color: black;
+ }
+ }
+
+ .collectionStackingView-sectionDelete {
+ position: absolute;
+ right: 0;
+ top: 0;
+ height: 100%;
+ }
+ }
+
+ .collectionStackingView-addDocumentButton,
+ .collectionStackingView-addGroupButton {
+ display: inline-block;
+ margin: 0 5px;
+ overflow: hidden;
+ width: 90%;
+ color: lightgrey;
+ overflow: ellipses;
+ }
+
+ .collectionStackingView-addGroupButton {
+ background: rgb(238, 238, 238);
+ font-size: 75%;
+ text-align: center;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ height: fit-content;
+
+ .editableView-container-editing-oneLine,
+ .editableView-container-editing {
+ color: grey;
+ padding: 10px;
+ }
+
+ .editableView-input:hover,
+ .editableView-container-editing:hover,
+ .editableView-container-editing-oneLine:hover {
+ cursor: text
+ }
+
+ .editableView-input {
+ padding: 12px 10px 11px 10px;
+ border: 0px;
+ color: grey;
+ // font-size: 75%;
+ text-align: center;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ outline-color: black;
+ }
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 54b0e37b5..e826d3ddf 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -2,10 +2,10 @@ import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, IReactionDisposer, reaction, untracked, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
+import { Doc, HeightSym, WidthSym, DocListCast } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
-import { BoolCast, NumCast, Cast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, Utils, returnTrue } from "../../../Utils";
+import { BoolCast, NumCast, Cast, StrCast, FieldValue } from "../../../new_fields/Types";
+import { emptyFunction, Utils } from "../../../Utils";
import { CollectionSchemaPreview } from "./CollectionSchemaView";
import "./CollectionStackingView.scss";
import { CollectionSubView } from "./CollectionSubView";
@@ -14,41 +14,81 @@ import { DragManager } from "../../util/DragManager";
import { DocumentType } from "../../documents/Documents";
import { Transform } from "../../util/Transform";
import { CursorProperty } from "csstype";
+import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
+import { listSpec } from "../../../new_fields/Schema";
+import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { List } from "../../../new_fields/List";
+import { EditableView } from "../EditableView";
+
+let valuesCreated = 1;
@observer
export class CollectionStackingView extends CollectionSubView(doc => doc) {
_masonryGridRef: HTMLDivElement | null = null;
_draggerRef = React.createRef<HTMLDivElement>();
_heightDisposer?: IReactionDisposer;
+ _sectionFilterDisposer?: IReactionDisposer;
_docXfs: any[] = [];
_columnStart: number = 0;
@observable private cursor: CursorProperty = "grab";
+ get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
@computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
@computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); }
@computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
@computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); }
@computed get columnWidth() { return this.singleColumn ? (this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin) : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); }
@computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
-
- @computed get Sections() {
+ get Sections() {
let sectionFilter = StrCast(this.props.Document.sectionFilter);
- let fields = new Map<object, Doc[]>();
- sectionFilter && this.filteredChildren.map(d => {
- let sectionValue = (d[sectionFilter] ? d[sectionFilter] : "-undefined-") as object;
- if (!fields.has(sectionValue)) fields.set(sectionValue, [d]);
- else fields.get(sectionValue)!.push(d);
- });
+ let sectionHeaders = this.sectionHeaders;
+ if (!sectionHeaders) {
+ this.props.Document.sectionHeaders = sectionHeaders = new List();
+ }
+ let fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []]));
+ if (sectionFilter) {
+ this.filteredChildren.map(d => {
+ let sectionValue = (d[sectionFilter] ? d[sectionFilter] : `No ${sectionFilter} value`) as object;
+ // the next five lines ensures that floating point rounding errors don't create more than one section -syip
+ let parsed = parseInt(sectionValue.toString());
+ let castedSectionValue: any = sectionValue;
+ if (!isNaN(parsed)) {
+ castedSectionValue = parsed;
+ }
+
+ // look for if header exists already
+ let existingHeader = sectionHeaders!.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `No ${sectionFilter} value`));
+ if (existingHeader) {
+ fields.get(existingHeader)!.push(d);
+ }
+ else {
+ let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `No ${sectionFilter} value`);
+ fields.set(newSchemaHeader, [d]);
+ sectionHeaders!.push(newSchemaHeader);
+ }
+ });
+ }
return fields;
}
componentDidMount() {
- this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
- () => this.singleColumn &&
- (this.props.Document.height = this.Sections.size * 50 + this.filteredChildren.reduce((height, d, i) =>
- height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap), this.yMargin))
- , { fireImmediately: true });
+ // is there any reason this needs to exist? -syip
+ // this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
+ // () => this.singleColumn &&
+ // (this.props.Document.height = this.Sections.size * 50 + this.filteredChildren.reduce((height, d, i) =>
+ // height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap), this.yMargin))
+ // , { fireImmediately: true });
+
+ // reset section headers when a new filter is inputted
+ this._sectionFilterDisposer = reaction(
+ () => StrCast(this.props.Document.sectionFilter),
+ () => {
+ this.props.Document.sectionHeaders = new List();
+ valuesCreated = 1;
+ }
+ )
}
componentWillUnmount() {
this._heightDisposer && this._heightDisposer();
+ this._sectionFilterDisposer && this._sectionFilterDisposer();
}
@action
@@ -66,7 +106,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
getDisplayDoc(layoutDoc: Doc, d: Doc, dxf: () => Transform) {
let resolvedDataDoc = !this.props.Document.isTemplate && this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
- let width = () => d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth;
+ let headings = Array.from(this.Sections.keys());
+ // let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+ let width = () => (d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth) / (headings.length + 1);
let height = () => this.getDocHeight(layoutDoc);
let finalDxf = () => dxf().scale(this.columnWidth / layoutDoc[WidthSym]());
return <CollectionSchemaPreview
@@ -97,49 +139,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
return (nw && nh) ? wid * aspect : d[HeightSym]();
}
- offsetTransform(doc: Doc, translateX: number, translateY: number) {
- let outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
- let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
- return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.columnWidth);
- }
- getDocTransform(doc: Doc, dref: HTMLDivElement) {
- let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
- return this.offsetTransform(doc, translateX, translateY);
- }
-
- getSingleDocTransform(doc: Doc, ind: number, width: number) {
- let localY = this.filteredChildren.reduce((height, d, i) =>
- height + (i < ind ? this.getDocHeight(Doc.expandTemplateLayout(d, this.props.DataDoc)) + this.gridGap : 0), this.yMargin);
- let translate = this.props.ScreenToLocalTransform().inverse().transformPoint((this.props.PanelWidth() - width) / 2, localY);
- return this.offsetTransform(doc, translate[0], translate[1]);
- }
-
- children(docs: Doc[]) {
- this._docXfs.length = 0;
- return docs.map((d, i) => {
- let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc);
- let width = () => d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth;
- let height = () => this.getDocHeight(layoutDoc);
- if (this.singleColumn) {
- //have to add the height of all previous single column sections or the doc decorations will be in the wrong place.
- let dxf = () => this.getSingleDocTransform(layoutDoc, i, width());
- let rowHgtPcnt = height();
- this._docXfs.push({ dxf: dxf, width: width, height: height });
- return <div className="collectionStackingView-columnDoc" key={d[Id]} style={{ width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: `${rowHgtPcnt}` }} >
- {this.getDisplayDoc(layoutDoc, d, dxf)}
- </div>;
- } else {
- let dref = React.createRef<HTMLDivElement>();
- let dxf = () => this.getDocTransform(layoutDoc, dref.current!);
- let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
- this._docXfs.push({ dxf: dxf, width: width, height: height });
- return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
- {this.getDisplayDoc(layoutDoc, d, dxf)}
- </div>;
- }
- });
- }
-
columnDividerDown = (e: React.PointerEvent) => {
e.stopPropagation();
e.preventDefault();
@@ -219,30 +218,46 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
});
}
- section(heading: string, docList: Doc[]) {
- let cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.filteredChildren.length,
+ section = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
+ let key = StrCast(this.props.Document.sectionFilter);
+ let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
+ let types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
+ if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
+ type = types[0];
+ }
+ let cols = () => this.singleColumn ? 1 : Math.max(1, Math.min(this.filteredChildren.length,
Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
- let templatecols = "";
- for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `;
- return <div key={heading}>
- {heading ? <div key={`${heading}`} className="collectionStackingView-sectionHeader">{heading}</div> : (null)}
- <div key={`${heading}-stack`} className={`collectionStackingView-masonry${this.singleColumn ? "Single" : "Grid"}`}
- style={{
- padding: this.singleColumn ? `${this.yMargin}px ${this.xMargin}px ${this.yMargin}px ${this.xMargin}px` : `${this.yMargin}px ${this.xMargin}px`,
- margin: "auto",
- width: this.singleColumn ? undefined : `${cols * (this.columnWidth + this.gridGap) + 2 * this.xMargin - this.gridGap}px`,
- height: 'max-content',
- position: "relative",
- gridGap: this.gridGap,
- gridTemplateColumns: this.singleColumn ? undefined : templatecols,
- gridAutoRows: this.singleColumn ? undefined : "0px"
- }}
- >
- {this.children(docList)}
- {this.singleColumn ? (null) : this.columnDragger}
- </div></div>;
+ return <CollectionStackingViewFieldColumn
+ key={heading ? heading.heading : ""}
+ cols={cols}
+ headings={() => Array.from(this.Sections.keys())}
+ heading={heading ? heading.heading : ""}
+ headingObject={heading}
+ docList={docList}
+ parent={this}
+ type={type}
+ createDropTarget={this.createDropTarget} />;
}
+
+ @action
+ addGroup = (value: string) => {
+ if (value) {
+ if (this.sectionHeaders) {
+ this.sectionHeaders.push(new SchemaHeaderField(value));
+ return true;
+ }
+ }
+ return false;
+ }
+
render() {
+ let headings = Array.from(this.Sections.keys());
+ let editableViewProps = {
+ GetValue: () => "",
+ SetValue: this.addGroup,
+ contents: "+ Add a Group"
+ }
+ // let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
return (
<div className="collectionStackingView"
ref={this.createRef} onDrop={this.onDrop.bind(this)} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
@@ -250,9 +265,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())],
["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)],
["height > width", this.filteredChildren.filter(f => f[WidthSym]() + 1 <= f[HeightSym]())]]. */}
- {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).
- map(section => this.section(section[0].toString(), section[1] as Doc[])) :
- this.section("", this.filteredChildren)}
+ {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort((a, b) => a[0].toString() > b[0].toString() ? 1 : -1).
+ map(section => this.section(section[0], section[1] as Doc[])) :
+ this.section(undefined, this.filteredChildren)}
+ {this.props.Document.sectionFilter ?
+ <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
+ style={{ width: (this.columnWidth / (headings.length + 1)) - 10, marginTop: 10 }}>
+ <EditableView {...editableViewProps} />
+ </div> : null}
</div>
);
}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
new file mode 100644
index 000000000..fe24c63c7
--- /dev/null
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -0,0 +1,250 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { number } from "prop-types";
+import { Doc, WidthSym } from "../../../new_fields/Doc";
+import { CollectionStackingView } from "./CollectionStackingView";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { Utils } from "../../../Utils";
+import { NumCast, StrCast } from "../../../new_fields/Types";
+import { EditableView } from "../EditableView";
+import { action, observable, computed } from "mobx";
+import { undoBatch } from "../../util/UndoManager";
+import { DragManager } from "../../util/DragManager";
+import { DocumentManager } from "../../util/DocumentManager";
+import { SelectionManager } from "../../util/SelectionManager";
+import "./CollectionStackingView.scss";
+import { Docs } from "../../documents/Documents";
+import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+
+interface CSVFieldColumnProps {
+ cols: () => number;
+ headings: () => object[];
+ heading: string;
+ headingObject: SchemaHeaderField | undefined;
+ docList: Doc[];
+ parent: CollectionStackingView;
+ type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined;
+ createDropTarget: (ele: HTMLDivElement) => void;
+}
+
+@observer
+export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldColumnProps> {
+ @observable private _background = "white";
+
+ private _dropRef: HTMLDivElement | null = null;
+ private dropDisposer?: DragManager.DragDropDisposer;
+
+ @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
+
+ createColumnDropRef = (ele: HTMLDivElement | null) => {
+ this._dropRef = ele;
+ this.dropDisposer && this.dropDisposer();
+ if (ele) {
+ this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.columnDrop.bind(this) } });
+ }
+ }
+
+ @undoBatch
+ @action
+ columnDrop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.data instanceof DragManager.DocumentDragData) {
+ let key = StrCast(this.props.parent.props.Document.sectionFilter);
+ let castedValue = this.getValue(this._heading);
+ if (castedValue) {
+ de.data.droppedDocuments.forEach(d => d[key] = castedValue);
+ }
+ else {
+ de.data.droppedDocuments.forEach(d => d[key] = undefined);
+ }
+ this.props.parent.drop(e, de);
+ e.stopPropagation();
+ }
+ }
+
+ children(docs: Doc[]) {
+ let style = this.props.parent;
+ this.props.parent._docXfs.length = 0;
+ return docs.map((d, i) => {
+ let layoutDoc = Doc.expandTemplateLayout(d, this.props.parent.props.DataDoc);
+ let headings = this.props.headings();
+ let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+ let width = () => (d.nativeWidth ? Math.min(layoutDoc[WidthSym](), style.columnWidth) : style.columnWidth) / (uniqueHeadings.length + 1);
+ let height = () => this.props.parent.getDocHeight(layoutDoc);
+ if (style.singleColumn) {
+ let dxf;
+ let dref = React.createRef<HTMLDivElement>();
+ if (uniqueHeadings.length > 0) {
+ dxf = () => this.getDocTransform(layoutDoc, dref.current!);
+ this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height });
+ }
+ else {
+ //have to add the height of all previous single column sections or the doc decorations will be in the wrong place.
+ dxf = () => this.getSingleDocTransform(layoutDoc, i, width());
+ this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height });
+ }
+ let rowHgtPcnt = height();
+ return <div className="collectionStackingView-columnDoc" key={d[Id]} ref={dref} style={{ width: width(), marginTop: i === 0 ? 0 : style.gridGap, height: `${rowHgtPcnt}` }} >
+ {this.props.parent.getDisplayDoc(layoutDoc, d, dxf)}
+ </div>;
+ } else {
+ let dref = React.createRef<HTMLDivElement>();
+ let dxf = () => this.getDocTransform(layoutDoc, dref.current!);
+ let rowSpan = Math.ceil((height() + style.gridGap) / style.gridGap);
+ this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height });
+ return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
+ {this.props.parent.getDisplayDoc(layoutDoc, d, dxf)}
+ </div>;
+ }
+ });
+ }
+
+ getSingleDocTransform(doc: Doc, ind: number, width: number) {
+ let localY = this.props.parent.filteredChildren.reduce((height, d, i) =>
+ height + (i < ind ? this.props.parent.getDocHeight(Doc.expandTemplateLayout(d, this.props.parent.props.DataDoc)) + this.props.parent.gridGap : 0), this.props.parent.yMargin);
+ let translate = this.props.parent.props.ScreenToLocalTransform().inverse().transformPoint((this.props.parent.props.PanelWidth() - width) / 2, localY);
+ return this.offsetTransform(doc, translate[0], translate[1]);
+ }
+
+ offsetTransform(doc: Doc, translateX: number, translateY: number) {
+ let outerXf = Utils.GetScreenTransform(this.props.parent._masonryGridRef!);
+ let offset = this.props.parent.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+ return this.props.parent.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.props.parent.columnWidth);
+ }
+
+ getDocTransform(doc: Doc, dref: HTMLDivElement) {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
+ return this.offsetTransform(doc, translateX, translateY);
+ }
+
+ getValue = (value: string): any => {
+ let parsed = parseInt(value);
+ if (!isNaN(parsed)) {
+ return parsed;
+ }
+ if (value.toLowerCase().indexOf("true") > -1) {
+ return true;
+ }
+ if (value.toLowerCase().indexOf("false") > -1) {
+ return false;
+ }
+ return value;
+ }
+
+ @action
+ headingChanged = (value: string, shiftDown?: boolean) => {
+ let key = StrCast(this.props.parent.props.Document.sectionFilter);
+ let castedValue = this.getValue(value);
+ if (castedValue) {
+ if (this.props.parent.sectionHeaders) {
+ if (this.props.parent.sectionHeaders.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
+ return false;
+ }
+ }
+ this.props.docList.forEach(d => d[key] = castedValue);
+ if (this.props.headingObject) {
+ this.props.headingObject.setHeading(castedValue.toString());
+ this._heading = this.props.headingObject.heading;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @action
+ pointerEntered = () => {
+ if (SelectionManager.GetIsDragging()) {
+ this._background = "#b4b4b4";
+ }
+ }
+
+ @action
+ pointerLeave = () => {
+ this._background = "white";
+ }
+
+ @action
+ addDocument = () => {
+ let key = StrCast(this.props.parent.props.Document.sectionFilter);
+ let newDoc = Docs.Create.TextDocument({ height: 18, title: "new text document" });
+ newDoc[key] = this.getValue(this.props.heading);
+ this.props.parent.props.addDocument(newDoc);
+ }
+
+ @action
+ deleteColumn = () => {
+ let key = StrCast(this.props.parent.props.Document.sectionFilter);
+ this.props.docList.forEach(d => d[key] = undefined);
+ if (this.props.parent.sectionHeaders && this.props.headingObject) {
+ let index = this.props.parent.sectionHeaders.indexOf(this.props.headingObject);
+ this.props.parent.sectionHeaders.splice(index, 1);
+ }
+ }
+
+ render() {
+ let cols = this.props.cols();
+ let key = StrCast(this.props.parent.props.Document.sectionFilter);
+ let templatecols = "";
+ let headings = this.props.headings();
+ let heading = this._heading;
+ let style = this.props.parent;
+ let singleColumn = style.singleColumn;
+ let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+ let evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `No ${key} value`;
+ let editableViewProps = {
+ GetValue: () => evContents,
+ SetValue: this.headingChanged,
+ contents: evContents,
+ oneLine: true
+ }
+ let headingView = this.props.headingObject ?
+ <div key={heading} className="collectionStackingView-sectionHeader"
+ style={{ width: (style.columnWidth) / (uniqueHeadings.length + 1) }}>
+ {/* the default bucket (no key value) has a tooltip that describes what it is.
+ Further, it does not have a color and cannot be deleted. */}
+ <div className="collectionStackingView-sectionHeader-subCont"
+ title={evContents === `No ${key} value` ?
+ `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
+ style={{
+ width: "100%",
+ background: this.props.headingObject && evContents !== `No ${key} value` ?
+ this.props.headingObject.color : "lightgrey",
+ color: "grey"
+ }}>
+ <EditableView {...editableViewProps} />
+ {evContents === `No ${key} value` ?
+ (null) :
+ <button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
+ <FontAwesomeIcon icon="trash" />
+ </button>}
+ </div>
+ </div> : (null);
+ for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth}px `;
+ return (
+ <div key={heading} style={{ width: `${100 / (uniqueHeadings.length + 1)}%`, background: this._background }}
+ ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
+ {headingView}
+ <div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`}
+ style={{
+ padding: singleColumn ? `${style.yMargin}px ${0}px ${style.yMargin}px ${0}px` : `${style.yMargin}px ${0}px`,
+ margin: "auto 5px",
+ width: singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+ height: 'max-content',
+ position: "relative",
+ gridGap: style.gridGap,
+ gridTemplateColumns: singleColumn ? undefined : templatecols,
+ gridAutoRows: singleColumn ? undefined : "0px"
+ }}
+ >
+ {this.children(this.props.docList)}
+ {singleColumn ? (null) : this.props.parent.columnDragger}
+ </div>
+ <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
+ style={{ width: style.columnWidth / (uniqueHeadings.length + 1) }}>
+ <button style={{ width: "100%" }} onClick={this.addDocument}>+ New</button>
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 4a51a1f58..a32f34215 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -17,6 +17,7 @@ import { CollectionStackingView } from './CollectionStackingView';
import { CollectionTreeView } from "./CollectionTreeView";
import { StrCast, PromiseValue } from '../../../new_fields/Types';
import { DocumentType } from '../../documents/Documents';
+import { CollectionStackingViewChrome } from './CollectionViewChromes';
export const COLLECTION_BORDER_WIDTH = 2;
library.add(faTh);
@@ -33,21 +34,40 @@ library.add(faImage);
export class CollectionView extends React.Component<FieldViewProps> {
public static LayoutString(fieldStr: string = "data", fieldExt: string = "") { return FieldView.LayoutString(CollectionView, fieldStr, fieldExt); }
- private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
+ private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
let props = { ...this.props, ...renderProps };
switch (this.isAnnotationOverlay ? CollectionViewType.Freeform : type) {
- case CollectionViewType.Schema: return (<CollectionSchemaView {...props} CollectionView={this} />);
- case CollectionViewType.Docking: return (<CollectionDockingView {...props} CollectionView={this} />);
- case CollectionViewType.Tree: return (<CollectionTreeView {...props} CollectionView={this} />);
- case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView {...props} CollectionView={this} />); }
- case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView {...props} CollectionView={this} />); }
+ case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} CollectionView={this} />);
+ case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} CollectionView={this} />);
+ case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} CollectionView={this} />);
+ case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} CollectionView={this} />); }
+ case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} CollectionView={this} />); }
case CollectionViewType.Freeform:
default:
- return (<CollectionFreeFormView {...props} CollectionView={this} />);
+ return (<CollectionFreeFormView key="collview" {...props} CollectionView={this} />);
}
return (null);
}
+ private Chrome = (type: CollectionViewType) => {
+ if (this.isAnnotationOverlay || this.props.Document === CurrentUserUtils.UserDocument.sidebar) {
+ return (null);
+ }
+
+ switch (type) {
+ case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this} />);
+ default:
+ return null;
+ }
+ }
+
+ private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
+ return [
+ this.Chrome(type),
+ this.SubViewHelper(type, renderProps)
+ ]
+ }
+
get isAnnotationOverlay() { return this.props.fieldExt ? true : false; }
onContextMenu = (e: React.MouseEvent): void => {
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
new file mode 100644
index 000000000..e5cb1b546
--- /dev/null
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -0,0 +1,54 @@
+@import "../globalCssVariables";
+
+.collectionStackingViewChrome {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ padding-bottom: 10px;
+ border-bottom: .5px solid lightgrey;
+ margin: 10px;
+
+ .collectionStackingViewChrome-sectionFilter-cont {
+ justify-self: right;
+ display: flex;
+ font-size: 75%;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+
+ .collectionStackingViewChrome-sectionFilter-label {
+ vertical-align: center;
+ padding: 10px;
+ }
+
+ .collectionStackingViewChrome-sectionFilter {
+ color: white;
+ width: fit-content;
+ text-align: center;
+ background: rgb(238, 238, 238);
+ height: 37px;
+
+ .editable-view-input,
+ input,
+ .editableView-container-editing-oneLine,
+ .editableView-container-editing {
+ padding: 12px 10px 11px 10px;
+ border: 0px;
+ color: grey;
+ text-align: center;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ outline-color: black;
+ height: 100%;
+ }
+
+ .react-autosuggest__container {
+ margin: 0;
+ color: grey;
+ padding: 0px;
+ }
+ }
+ }
+
+ .collectionStackingViewChrome-sectionFilter:hover {
+ cursor: text;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
new file mode 100644
index 000000000..52fee26bf
--- /dev/null
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -0,0 +1,152 @@
+import * as React from "react";
+import { CollectionView } from "./CollectionView";
+import "./CollectionViewChromes.scss";
+import { CollectionViewType } from "./CollectionBaseView";
+import { undoBatch } from "../../util/UndoManager";
+import { action, observable, runInAction, computed } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { DocLike } from "../MetadataEntryMenu";
+const higflyout = require("@hig/flyout");
+export const Flyout = higflyout.default;
+import * as Autosuggest from 'react-autosuggest';
+import { EditableView } from "../EditableView";
+import { StrCast } from "../../../new_fields/Types";
+
+interface CollectionViewChromeProps {
+ CollectionView: CollectionView;
+}
+
+class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
+ @undoBatch
+ viewChanged = (e: React.ChangeEvent) => {
+ //@ts-ignore
+ switch (e.target.selectedOptions[0].value) {
+ case "freeform":
+ this.props.CollectionView.props.Document.viewType = CollectionViewType.Freeform;
+ break;
+ case "schema":
+ this.props.CollectionView.props.Document.viewType = CollectionViewType.Schema;
+ break;
+ case "treeview":
+ this.props.CollectionView.props.Document.viewType = CollectionViewType.Tree;
+ break;
+ case "stacking":
+ this.props.CollectionView.props.Document.viewType = CollectionViewType.Stacking;
+ break;
+ case "masonry":
+ this.props.CollectionView.props.Document.viewType = CollectionViewType.Masonry;
+ break;
+ default:
+ break;
+ }
+ }
+
+ render() {
+ return (
+ <div className="collectionViewBaseChrome">
+ <select onChange={this.viewChanged}>
+ <option value="freeform" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Freeform}>Freeform View</option>
+ <option value="schema" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Schema}>Schema View</option>
+ <option value="treeview" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Tree}>Tree View</option>
+ <option value="stacking" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Stacking}>Stacking View</option>
+ <option value="masonry" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Masonry}>Masonry View</option>
+ </select>
+ </div>
+ )
+ }
+}
+
+@observer
+export class CollectionStackingViewChrome extends React.Component<CollectionViewChromeProps> {
+ @observable private _currentKey: string = "";
+ @observable private suggestions: string[] = [];
+
+ @computed get sectionFilter() { return StrCast(this.props.CollectionView.props.Document.sectionFilter); }
+
+ getKeySuggestions = async (value: string): Promise<string[]> => {
+ value = value.toLowerCase();
+ let docs: Doc | Doc[] | Promise<Doc> | Promise<Doc[]> | (() => DocLike)
+ = () => DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldExt ? this.props.CollectionView.props.fieldExt : this.props.CollectionView.props.fieldKey]);
+ if (typeof docs === "function") {
+ docs = docs();
+ }
+ docs = await docs;
+ if (docs instanceof Doc) {
+ return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value));
+ } else {
+ const keys = new Set<string>();
+ docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+ return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
+ }
+ }
+
+ @action
+ onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
+ this._currentKey = newValue;
+ }
+
+ getSuggestionValue = (suggestion: string) => suggestion;
+
+ renderSuggestion = (suggestion: string) => {
+ return <p>{suggestion}</p>;
+ }
+
+ onSuggestionFetch = async ({ value }: { value: string }) => {
+ const sugg = await this.getKeySuggestions(value);
+ runInAction(() => {
+ this.suggestions = sugg;
+ });
+ }
+
+ @action
+ onSuggestionClear = () => {
+ this.suggestions = [];
+ }
+
+ setValue = (value: string) => {
+ this.props.CollectionView.props.Document.sectionFilter = value;
+ return true;
+ }
+
+ @action resetValue = () => { this._currentKey = this.sectionFilter; };
+
+ render() {
+ return (
+ <div className="collectionStackingViewChrome">
+ <CollectionViewBaseChrome CollectionView={this.props.CollectionView} />
+ <div className="collectionStackingViewChrome-sectionFilter-cont">
+ <div className="collectionStackingViewChrome-sectionFilter-label">
+ Group items by:
+ </div>
+ <div className="collectionStackingViewChrome-sectionFilter">
+ <EditableView
+ GetValue={() => this.sectionFilter}
+ autosuggestProps={
+ {
+ resetValue: this.resetValue,
+ value: this._currentKey,
+ onChange: this.onKeyChange,
+ autosuggestProps: {
+ inputProps:
+ {
+ value: this._currentKey,
+ onChange: this.onKeyChange
+ },
+ getSuggestionValue: this.getSuggestionValue,
+ suggestions: this.suggestions,
+ alwaysRenderSuggestions: true,
+ renderSuggestion: this.renderSuggestion,
+ onSuggestionsFetchRequested: this.onSuggestionFetch,
+ onSuggestionsClearRequested: this.onSuggestionClear
+ }
+ }}
+ SetValue={this.setValue}
+ contents={this.sectionFilter ? this.sectionFilter : "N/A"}
+ />
+ </div>
+ </div>
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6bb082b66..71d8988bf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,4 +1,4 @@
-import { action, computed } from "mobx";
+import { action, computed, trace } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
@@ -517,7 +517,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
]
render() {
const easing = () => this.props.Document.panTransformType === "Ease";
-
Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
return (
<div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 09a1b49fe..2999babf1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -295,6 +295,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (this._doubleTap && this.props.renderDepth) {
let fullScreenAlias = Doc.MakeAlias(this.props.Document);
fullScreenAlias.templates = new List<string>();
+ if (this.props.Document.layout === this.props.Document.miniLayout) {
+ Doc.ToggleDetailLayout(fullScreenAlias);
+ }
this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab");
SelectionManager.DeselectAll();
this.props.Document.libraryBrush = undefined;
@@ -616,7 +619,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@computed get nativeWidth() { return this.Document.nativeWidth || 0; }
@computed get nativeHeight() { return this.Document.nativeHeight || 0; }
@computed get contents() {
- return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} selectOnLoad={this.props.selectOnLoad} layoutKey={"layout"} DataDoc={this.dataDoc} />);
+ return (<DocumentContentsView {...this.props}
+ isSelected={this.isSelected} select={this.select}
+ selectOnLoad={this.props.selectOnLoad}
+ layoutKey={"layout"}
+ fitToBox={BoolCast(this.props.Document.fitToBox) ? true : this.props.fitToBox}
+ DataDoc={this.dataDoc} />);
}
render() {
diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts
new file mode 100644
index 000000000..284de3023
--- /dev/null
+++ b/src/new_fields/SchemaHeaderField.ts
@@ -0,0 +1,46 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, createSimpleSchema, primitive } from "serializr";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString, OnUpdate } from "./FieldSymbols";
+import { scriptingGlobal, Scripting } from "../client/util/Scripting";
+
+export const PastelSchemaPalette = new Map<string, string>([
+ ["purple", "#f5b5fc"],
+ ["green", "#96F7D2"],
+ ["yellow", "#F0F696"],
+ ["red", "#FCB1B1"]
+])
+
+@scriptingGlobal
+@Deserializable("schemaheader")
+export class SchemaHeaderField extends ObjectField {
+ @serializable(primitive())
+ heading: string;
+ color: string;
+
+ constructor(heading: string = "", color: string = Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * 4)]) {
+ super();
+
+ this.heading = heading;
+ this.color = color;
+ }
+
+ setHeading(heading: string) {
+ this.heading = heading;
+ this[OnUpdate]();
+ }
+
+ setColor(color: string) {
+ this.color = color;
+ this[OnUpdate]();
+ }
+
+ [Copy]() {
+ return new SchemaHeaderField(this.heading, this.color);
+ }
+
+ [ToScriptString]() {
+ return `invalid`;
+ }
+}
+