diff options
-rw-r--r-- | src/client/documents/Documents.ts | 4 | ||||
-rw-r--r-- | src/client/views/EditableView.scss | 10 | ||||
-rw-r--r-- | src/client/views/EditableView.tsx | 30 | ||||
-rw-r--r-- | src/client/views/MetadataEntryMenu.tsx | 1 | ||||
-rw-r--r-- | src/client/views/collections/CollectionBaseView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSchemaView.tsx | 3 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingView.scss | 136 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingView.tsx | 188 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 250 | ||||
-rw-r--r-- | src/client/views/collections/CollectionView.tsx | 34 | ||||
-rw-r--r-- | src/client/views/collections/CollectionViewChromes.scss | 54 | ||||
-rw-r--r-- | src/client/views/collections/CollectionViewChromes.tsx | 152 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 3 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 10 | ||||
-rw-r--r-- | src/new_fields/SchemaHeaderField.ts | 46 |
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`; + } +} + |