aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/collections/CollectionStackingView.scss99
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx115
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx64
-rw-r--r--src/new_fields/SchemaHeaderField.ts46
4 files changed, 266 insertions, 58 deletions
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 3e389225a..e0ced8af4 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -6,6 +6,7 @@
position: absolute;
display: flex;
overflow-y: auto;
+ flex-wrap: wrap;
.collectionStackingView-docView-container {
width: 45%;
@@ -67,12 +68,6 @@
display: inline-block;
}
- .collectionStackingView-columnDoc,
- .collectionStackingView-masonryDoc {
- margin-left: auto;
- margin-right: auto;
- }
-
.collectionStackingView-masonryDoc {
transform-origin: top left;
grid-column-end: span 1;
@@ -80,27 +75,103 @@
}
.collectionStackingView-sectionHeader {
- background: gray;
text-align: center;
- margin-left: 10px;
- margin-right: 10px;
+ margin-left: 5px;
+ margin-right: 5px;
margin-top: 10px;
- color: $light-color;
- text-transform: uppercase;
- letter-spacing: 2px;
- padding: 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 10px;
+ 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 0ddd5528b..e4e6f92e3 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -2,9 +2,9 @@ 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 { BoolCast, NumCast, Cast, StrCast, FieldValue } from "../../../new_fields/Types";
import { emptyFunction, Utils } from "../../../Utils";
import { CollectionSchemaPreview } from "./CollectionSchemaView";
import "./CollectionStackingView.scss";
@@ -15,46 +15,80 @@ 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;
- let parsed = parseInt(sectionValue.toString());
- let castedSectionValue: any = sectionValue;
- if (!isNaN(parsed)) {
- castedSectionValue = parsed;
- }
- if (!fields.has(castedSectionValue)) fields.set(castedSectionValue, [d]);
- else fields.get(castedSectionValue)!.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
@@ -73,8 +107,8 @@ 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 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) / (uniqueHeadings.length + 1);
+ // 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
@@ -183,23 +217,21 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
});
}
- section = (heading: string, docList: Doc[]) => {
+ section = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
let key = StrCast(this.props.Document.sectionFilter);
- let types = docList.map(d => typeof d[key]);
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 parsed = parseInt(heading);
- if (!isNaN(parsed)) {
- heading = parsed.toString();
- }
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))));
return <CollectionStackingViewFieldColumn
+ key={heading ? heading.heading : ""}
cols={cols}
headings={() => Array.from(this.Sections.keys())}
- heading={heading}
+ heading={heading ? heading.heading : ""}
+ headingObject={heading}
docList={docList}
parent={this}
type={type}
@@ -207,13 +239,24 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
@action
- addGroup = () => {
-
+ 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 uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+ 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()} >
@@ -222,12 +265,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
["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()).sort((a, b) => a[0].toString() > b[0].toString() ? 1 : -1).
- map(section => this.section(section[0].toString(), section[1] as Doc[])) :
- this.section("", this.filteredChildren)}
+ 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 / (uniqueHeadings.length + 1), marginTop: 10 }}>
- <button style={{ width: "100%" }} onClick={this.addGroup}>+ Add a Group</button>
+ 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
index 9f64a4e93..fe24c63c7 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -7,19 +7,22 @@ import { Id } from "../../../new_fields/FieldSymbols";
import { Utils } from "../../../Utils";
import { NumCast, StrCast } from "../../../new_fields/Types";
import { EditableView } from "../EditableView";
-import { action, observable } from "mobx";
+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;
@@ -33,6 +36,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
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();
@@ -46,10 +51,13 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
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.props.heading);
+ 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();
}
@@ -124,11 +132,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
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;
@@ -154,23 +172,53 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
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.props.heading;
+ 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: () => heading,
+ GetValue: () => evContents,
SetValue: this.headingChanged,
- contents: heading,
+ contents: evContents,
+ oneLine: true
}
- let headingView = heading ?
+ let headingView = this.props.headingObject ?
<div key={heading} className="collectionStackingView-sectionHeader"
style={{ width: (style.columnWidth) / (uniqueHeadings.length + 1) }}>
- <EditableView {...editableViewProps} />
+ {/* 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 (
@@ -180,7 +228,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
<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",
+ margin: "auto 5px",
width: singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
height: 'max-content',
position: "relative",
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`;
+ }
+}
+