diff options
20 files changed, 420 insertions, 182 deletions
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 3cc70382c..450931b76 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -67,6 +67,8 @@ export class TooltipTextMenu { @observable private _storedMarks: Mark<any>[] | null | undefined; + public HackToFixTextSelectionGlitch: boolean = false; + constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) { this.view = view; @@ -191,6 +193,10 @@ export class TooltipTextMenu { this.updateListItemDropdown(":", this.listTypeBtnDom); this.update(view, undefined); + + // add tooltip to outerdiv to circumvent scaling problem + const outer_div = this.editorProps.outer_div; + outer_div && outer_div(this.wrapper); } //label of dropdown will change to given label @@ -850,7 +856,8 @@ export class TooltipTextMenu { this.updateFontSizeDropdown("Various"); } } - this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks)); + !this.HackToFixTextSelectionGlitch && + this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks)); // bcz: what's the purpose of this line? It messes up text selection without the Hack. this.update_mark_doms(); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index d537e2dac..df526e01c 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -523,7 +523,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> break; } - runInAction(() => FormattedTextBox.InputBoxOverlay = undefined); + if (!this._resizing) runInAction(() => FormattedTextBox.InputBoxOverlay = undefined); SelectionManager.SelectedDocuments().forEach(element => { if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { let doc = PositionDocument(element.props.Document); diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss index d95398f17..5437b26d6 100644 --- a/src/client/views/InkingCanvas.scss +++ b/src/client/views/InkingCanvas.scss @@ -21,8 +21,7 @@ width: 8192px; height: 8192px; cursor: "crosshair"; - pointer-events: auto; - + pointer-events: all; } .inkingCanvas-canSelect, diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index b08133d80..1cfa8d644 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -170,12 +170,12 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { return [!penPaths.length ? (null) : <svg className={`inkingCanvas-paths-ink`} key="Pens" style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} > - {} + {penPaths} </svg>, !markerPaths.length ? (null) : <svg className={`inkingCanvas-paths-markers`} key="Markers" style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}> - {} + {markerPaths} </svg>]; } diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 846f1d8aa..f315b6d59 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -133,22 +133,23 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> render() { this.TextDoc; this.TextDataDoc; if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) { + let wid = FormattedTextBox.InputBoxOverlay.props.Document.width; // need to force overlay to render when underlying text box is resized (eg, w/ DocDecorations) let textRect = this._textTargetDiv.getBoundingClientRect(); let s = this._textXf().Scale; let location = this._textBottom ? textRect.bottom : textRect.top; let hgt = this._textAutoHeight || this._textBottom ? "auto" : this._textTargetDiv.clientHeight; return <div ref={this._setouterdiv} className="mainOverlayTextBox-unscaled_div" style={{ transform: `translate(${textRect.left}px, ${location}px)` }} > - <div className="mainOverlayTextBox-textInput" style={{ transform: `scale(${1 / s},${1 / s})`, width: "auto", height: "0px" }} > + <div className="mainOverlayTextBox-textInput" style={{ transform: `scale(${1 / s})`, width: "auto", height: "0px" }} > <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ width: `${textRect.width * s}px`, height: "0px" }}> <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}> <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} fieldExt="" hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc} - onClick={emptyFunction} + onClick={undefined} isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne} - ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} /> + ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} /> </div> </div> </div> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5b19e95ed..7b4f1fc52 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -323,7 +323,7 @@ export class MainView extends React.Component { DataDoc={undefined} addDocument={undefined} addDocTab={emptyFunction} - onClick={emptyFunction} + onClick={undefined} removeDocument={undefined} ScreenToLocalTransform={Transform.Identity} ContentScaling={returnOne} @@ -388,7 +388,7 @@ export class MainView extends React.Component { addDocument={undefined} addDocTab={this.addDocTabFunc} removeDocument={undefined} - onClick={emptyFunction} + onClick={undefined} ScreenToLocalTransform={Transform.Identity} ContentScaling={returnOne} PanelWidth={this.flyoutWidthFunc} diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index cad87ebcc..4a296493a 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -83,7 +83,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { active = (): boolean => { var isSelected = this.props.isSelected(); - return isSelected || this._isChildActive || this.props.renderDepth === 0 || BoolCast(this.props.Document.excludeFromLibrary); + return isSelected || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0 || BoolCast(this.props.Document.excludeFromLibrary); } //TODO should this be observable? diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 13bb34037..7332a490a 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -389,7 +389,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp const stack = tab.contentItem.parent; // shifts the focus to this tab when another tab is dragged over it tab.element[0].onmouseenter = (e: any) => { - if (!this._isPointerDown) return; + if (!this._isPointerDown || !SelectionManager.GetIsDragging()) return; var activeContentItem = tab.header.parent.getActiveContentItem(); if (tab.contentItem !== activeContentItem) { tab.header.parent.setActiveContentItem(tab.contentItem); diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 271ad2d58..01d4ea2b6 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -1,10 +1,15 @@ @import "../globalCssVariables"; -.collectionStackingView { +.collectionMasonryView { + display:inline; +} +.collectionStackingView{ + display: flex; +} +.collectionStackingView, .collectionMasonryView{ height: 100%; width: 100%; position: absolute; - display: flex; top: 0; overflow-y: auto; flex-wrap: wrap; @@ -31,14 +36,20 @@ .collectionStackingView-masonrySingle, .collectionStackingView-masonryGrid { width: 100%; - height: 100%; - position: absolute; display: grid; top: 0; left: 0; - width: 100%; + } + .collectionStackingView-masonrySingle { + height: 100%; position: absolute; } + .collectionStackingView-masonryGrid { + margin: auto; + height: max-content; + position: relative; + grid-auto-rows: 0px; + } .collectionStackingView-masonrySingle { width: 100%; @@ -80,12 +91,17 @@ height: 100%; margin: auto; } + + .collectionStackingView-masonrySection { + margin: auto; + } .collectionStackingView-sectionHeader { text-align: center; margin-left: 2px; margin-right: 2px; margin-top: 10px; + background: gray; // overflow: hidden; overflow is visible so the color menu isn't hidden -ftong .editableView-input { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index ba3d8e6a7..c3c1f2108 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -10,7 +10,7 @@ import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../new_fields/Types"; -import { emptyFunction } from "../../../Utils"; +import { emptyFunction, Utils, numberRange } from "../../../Utils"; import { DocumentType } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; @@ -33,14 +33,21 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { _docXfs: any[] = []; _columnStart: number = 0; @observable private cursor: CursorProperty = "grab"; - get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); } + @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); } + @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); } + @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); } @computed get 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 sectionFilter() { return this.singleColumn ? StrCast(this.props.Document.sectionFilter) : ""; } + @computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); } + @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; } + @computed get showAddAGroup() { return (this.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')); } + @computed get columnWidth() { + return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin, + this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250)); + } + + childDocHeight(child: Doc) { return this.getDocHeight(Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, child).layout); } get layoutDoc() { // if this document's layout field contains a document (ie, a rendering template), then we will use that @@ -48,12 +55,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; } - get Sections() { - if (!this.sectionFilter) return new Map<SchemaHeaderField, Doc[]>(); + if (!this.sectionFilter || this.sectionHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>(); if (this.sectionHeaders === undefined) { - this.props.Document.sectionHeaders = new List<SchemaHeaderField>(); + setTimeout(() => this.props.Document.sectionHeaders = new List<SchemaHeaderField>(), 0); + return new Map<SchemaHeaderField, Doc[]>(); } const sectionHeaders = this.sectionHeaders!; let fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); @@ -77,23 +84,32 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return fields; } + childLayoutDisposer?: IReactionDisposer; componentDidMount() { - // is there any reason this needs to exist? -syip + this.childLayoutDisposer = reaction(() => this.props.Document.childLayout, + async () => { + let chidlLayout = Cast(this.props.Document.childLayout, Doc); + if (chidlLayout instanceof Doc) { + const childLayout = chidlLayout; + let list = await this.childDocList; + list && list.map(async doc => { + !Doc.AreProtosEqual(childLayout, (await doc).layout as Doc) && Doc.ApplyTemplateTo(childLayout, (await doc), undefined); + }); + } + }); + // is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported). this._heightDisposer = reaction(() => { - if (this.singleColumn && BoolCast(this.props.Document.autoHeight)) { - let hgt = this.Sections.size * 50 + this.filteredChildren.reduce((height, d, i) => { - let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d); - return height + this.getDocHeight(pair.layout) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap); - }, this.yMargin); - return hgt * this.props.ContentScaling(); + if (this.isStackingView && BoolCast(this.props.Document.autoHeight)) { + let sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]); + return this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => Math.max(maxHght, + 50 + s.reduce((height, d, i) => height + this.childDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap), this.yMargin) + ), 0); } return -1; }, (hgt: number) => { - if (hgt !== -1) { - let doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; - doc.height = hgt; - } + let doc = hgt === -1 ? undefined : this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; + doc && (doc.height = hgt); }, { fireImmediately: true } ); @@ -105,6 +121,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { ); } componentWillUnmount() { + this.childLayoutDisposer && this.childLayoutDisposer(); this._heightDisposer && this._heightDisposer(); this._sectionFilterDisposer && this._sectionFilterDisposer(); } @@ -149,12 +166,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { previewScript={undefined}> </CollectionSchemaPreview>; } - getDocHeight(d: Doc, columnScale: number = 1) { + getDocHeight(d: Doc) { let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); if (!d.ignoreAspect && nw && nh) { let aspect = nw && nh ? nh / nw : 1; - let wid = this.props.Document.fillColumn ? this.columnWidth / columnScale : Math.min(Math.min(d[WidthSym](), NumCast(d.nativeWidth)), this.columnWidth / columnScale); + let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); + if (!(d.nativeWidth && !d.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(d[WidthSym](), wid); return wid * aspect; } return d[HeightSym](); @@ -240,14 +258,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { }); } headings = () => Array.from(this.Sections.keys()); - section = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { + sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { let key = this.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, + let cols = () => this.isStackingView ? 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 : ""} @@ -263,6 +281,55 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { />; } + getDocTransform(doc: Doc, dref: HTMLDivElement) { + let { scale, translateX, translateY } = Utils.GetScreenTransform(dref); + 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); + } + masonryChildren(docs: Doc[]) { + this._docXfs.length = 0; + return docs.map((d, i) => { + let dref = React.createRef<HTMLDivElement>(); + let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc); + let width = () => (d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth);/// (uniqueHeadings.length + 1); + let height = () => this.getDocHeight(layoutDoc); + 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, width)} + </div>; + }); + } + + @observable _headingsHack: number = 1; + sectionMasonry(heading: SchemaHeaderField | undefined, docList: Doc[]) { + let cols = Math.max(1, Math.min(docList.length, + Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); + return <div key={heading ? heading.heading : "empty"} className="collectionStackingView-masonrySection"> + {!heading ? (null) : + <div key={`${heading.heading}`} className="collectionStackingView-sectionHeader" style={{ background: heading.color }} + onClick={action(() => this._headingsHack++ && heading.setCollapsed(!heading.collapsed))} > + {heading.heading} + </div>} + {this._headingsHack && heading && heading.collapsed ? (null) : + <div key={`${heading}-stack`} className={`collectionStackingView-masonryGrid`} + style={{ + padding: `${this.yMargin}px ${this.xMargin}px`, + width: `${cols * (this.columnWidth + this.gridGap) + 2 * this.xMargin - this.gridGap}px`, + gridGap: this.gridGap, + gridTemplateColumns: numberRange(cols).reduce((list, i) => list + ` ${this.columnWidth}px`, ""), + }}> + {this.masonryChildren(docList)} + {this.columnDragger} + </div> + } + </div>; + } + @action addGroup = (value: string) => { if (value && this.sectionHeaders) { @@ -296,7 +363,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } render() { - let headings = Array.from(this.Sections.keys()); let editableViewProps = { GetValue: () => "", SetValue: this.addGroup, @@ -304,18 +370,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { }; Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); - // let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); + let sections = (this.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc) : [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]]); return ( - <div className="collectionStackingView" + <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"} ref={this.createRef} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={(e: React.WheelEvent) => e.stopPropagation()} > - {this.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc). - map((section: [SchemaHeaderField, Doc[]]) => this.section(section[0], section[1])) : - this.section(undefined, this.filteredChildren)} - {(this.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ? + {sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]))} + {!this.showAddAGroup ? (null) : <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" - style={{ width: (this.columnWidth / (headings.length + ((this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0))) - 10, marginTop: 10 }}> + style={{ width: this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}> <EditableView {...editableViewProps} /> - </div> : null} + </div>} {this.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? <Switch onChange={this.onToggle} onClick={this.onToggle} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 9824a501b..74c7ef305 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -1,28 +1,25 @@ import React = require("react"); +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faPalette } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, observable } from "mobx"; 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 { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { NumCast, StrCast } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; -import { NumCast, StrCast, BoolCast } 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, PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { ScriptField } from "../../../new_fields/ScriptField"; +import { DragManager } from "../../util/DragManager"; import { CompileScript } from "../../util/Scripting"; -import { RichTextField } from "../../../new_fields/RichTextField"; +import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; -import { Flyout, anchorPoints } from "../DocumentDecorations"; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faPalette } from '@fortawesome/free-solid-svg-icons'; +import { undoBatch } from "../../util/UndoManager"; +import { anchorPoints, Flyout } from "../DocumentDecorations"; +import { EditableView } from "../EditableView"; +import { CollectionStackingView } from "./CollectionStackingView"; +import "./CollectionStackingView.scss"; library.add(faPalette); @@ -81,38 +78,17 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC let parent = this.props.parent; parent._docXfs.length = 0; return docs.map((d, i) => { - let headings = this.props.headings(); - let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); let pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d); - let width = () => (d.nativeWidth && !d.ignoreAspect && !parent.props.Document.fillColumn ? Math.min(Math.min(d[WidthSym](), NumCast(d.nativeWidth)), parent.columnWidth / (uniqueHeadings.length + 1)) : parent.columnWidth / (uniqueHeadings.length + 1));/// (uniqueHeadings.length + 1); - let height = () => parent.getDocHeight(pair.layout, uniqueHeadings.length + 1);// / (d.nativeWidth && !BoolCast(d.ignoreAspect) ? uniqueHeadings.length + 1 : 1); + let width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !parent.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, parent.columnWidth / parent.numGroupColumns); + let height = () => parent.getDocHeight(pair.layout); let dref = React.createRef<HTMLDivElement>(); - // if (uniqueHeadings.length > 0) { let dxf = () => this.getDocTransform(pair.layout, 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. - // let dxf = () => this.getDocTransform(layoutDoc, i, width()); - // this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height }); - // } - let rowHgtPcnt = height(); let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap); - let style = parent.singleColumn ? { width: width(), margin: "auto", marginTop: i === 0 ? 0 : parent.gridGap, height: `${rowHgtPcnt}` } : { gridRowEnd: `span ${rowSpan}` }; - return <div className={`collectionStackingView-${parent.singleColumn ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} > + let style = parent.isStackingView ? { width: width(), margin: "auto", marginTop: i === 0 ? 0 : parent.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` }; + return <div className={`collectionStackingView-${parent.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} > {this.props.parent.getDisplayDoc(pair.layout, pair.data, dxf, width)} </div>; - // } else { - // let dref = React.createRef<HTMLDivElement>(); - // let dxf = () => this.getDocTransform(layoutDoc, dref.current!); - // this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height }); - // let rowHgtPcnt = height(); - // let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap); - // let divStyle = parent.singleColumn ? { width: width(), marginTop: i === 0 ? 0 : parent.gridGap, height: `${rowHgtPcnt}` } : { gridRowEnd: `span ${rowSpan}` }; - // return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={divStyle} > - // {this.props.parent.getDisplayDoc(layoutDoc, d, dxf, width)} - // </div>; - // } }); } @@ -278,7 +254,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC let headings = this.props.headings(); let heading = this._heading; let style = this.props.parent; - let singleColumn = style.singleColumn; + let singleColumn = style.isStackingView; let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); let evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`; let headerEditableViewProps = { @@ -326,7 +302,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC </button>} </div> </div> : (null); - for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth}px `; + for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `; return ( <div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }} ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}> @@ -348,7 +324,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC </div> {(this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" - style={{ width: style.columnWidth / (uniqueHeadings.length + 1) }}> + style={{ width: style.columnWidth / style.numGroupColumns }}> <EditableView {...newEditableViewProps} /> </div> : null} </div> diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 24bd24d11..4b1fca18a 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -25,7 +25,7 @@ import { CollectionSchemaPreview } from './CollectionSchemaView'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import React = require("react"); -import { ComputedField } from '../../../new_fields/ScriptField'; +import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; import { KeyValueBox } from '../nodes/KeyValueBox'; @@ -400,21 +400,56 @@ class TreeView extends React.Component<TreeViewProps> { panelWidth: () => number, renderDepth: number ) { - let docList = docs.filter(child => !child.excludeFromLibrary); + let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); + if (viewSpecScript) { + let script = viewSpecScript.script; + docs = docs.filter(d => { + let res = script.run({ doc: d }); + if (res.success) { + return res.result; + } + else { + console.log(res.error); + } + }); + } + + let descending = BoolCast(containingCollection.stackingHeadersSortDescending); + docs.sort(function (a, b): 1 | -1 { + let descA = descending ? b : a; + let descB = descending ? a : b; + let first = descA[String(containingCollection.sectionFilter)]; + let second = descB[String(containingCollection.sectionFilter)]; + // TODO find better way to sort how to sort.................. + if (typeof first === 'number' && typeof second === 'number') { + return (first - second) > 0 ? 1 : -1; + } + if (typeof first === 'string' && typeof second === 'string') { + return first > second ? 1 : -1; + } + if (typeof first === 'boolean' && typeof second === 'boolean') { + // if (first === second) { // bugfixing?: otherwise, the list "flickers" because the list is resorted during every load + // return Number(descA.x) > Number(descB.x) ? 1 : -1; + // } + return first > second ? 1 : -1; + } + return descending ? 1 : -1; + }); + let rowWidth = () => panelWidth() - 20; - return docList.map((child, i) => { + return docs.map((child, i) => { let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child); let indent = i === 0 ? undefined : () => { - if (StrCast(docList[i - 1].layout).indexOf("CollectionView") !== -1) { - let fieldKeysub = StrCast(docList[i - 1].layout).split("fieldKey")[1]; + if (StrCast(docs[i - 1].layout).indexOf("CollectionView") !== -1) { + let fieldKeysub = StrCast(docs[i - 1].layout).split("fieldKey")[1]; let fieldKey = fieldKeysub.split("\"")[1]; - Doc.AddDocToList(docList[i - 1], fieldKey, child); + Doc.AddDocToList(docs[i - 1], fieldKey, child); remove(child); } }; let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => { - return add(doc, relativeTo ? relativeTo : docList[i], before !== undefined ? before : false); + return add(doc, relativeTo ? relativeTo : docs[i], before !== undefined ? before : false); }; let rowHeight = () => { let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index aaaa623fb..7e1adaa19 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -109,6 +109,7 @@ export class CollectionView extends React.Component<FieldViewProps> { let existing = ContextMenu.Instance.findByDescription("Layout..."); let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: "Create Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); + layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" }); } } diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index 2006c08f3..595f079d1 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -42,6 +42,14 @@ transform-origin: top left; // margin-top: 10px; } + .collectionViewBaseChrome-template { + margin-left: 10px; + display: grid; + background: rgb(238, 238, 238); + color:grey; + margin-top:auto; + margin-bottom:auto; + } .collectionViewBaseChrome-viewSpecs { margin-left: 10px; @@ -106,17 +114,20 @@ } - .collectionStackingViewChrome-cont { + .collectionStackingViewChrome-cont, + .collectionTreeViewChrome-cont { display: flex; justify-content: space-between; } - .collectionStackingViewChrome-sort { + .collectionStackingViewChrome-sort, + .collectionTreeViewChrome-sort { display: flex; align-items: center; justify-content: space-between; - .collectionStackingViewChrome-sortIcon { + .collectionStackingViewChrome-sortIcon, + .collectionTreeViewChrome-sortIcon { transition: transform .5s; margin-left: 10px; } @@ -127,18 +138,21 @@ } - .collectionStackingViewChrome-sectionFilter-cont { + .collectionStackingViewChrome-sectionFilter-cont, + .collectionTreeViewChrome-sectionFilter-cont { justify-self: right; display: flex; font-size: 75%; letter-spacing: 2px; - .collectionStackingViewChrome-sectionFilter-label { + .collectionStackingViewChrome-sectionFilter-label, + .collectionTreeViewChrome-sectionFilter-label { vertical-align: center; padding: 10px; } - .collectionStackingViewChrome-sectionFilter { + .collectionStackingViewChrome-sectionFilter, + .collectionTreeViewChrome-sectionFilter { color: white; width: 100px; text-align: center; @@ -165,7 +179,8 @@ } } - .collectionStackingViewChrome-sectionFilter:hover { + .collectionStackingViewChrome-sectionFilter:hover, + .collectionTreeViewChrome-sectionFilter:hover { cursor: text; } } diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 84f64e2cf..6ea718330 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -21,6 +21,7 @@ import { listSpec } from "../../../new_fields/Schema"; import { List } from "../../../new_fields/List"; import { Id } from "../../../new_fields/FieldSymbols"; import { threadId } from "worker_threads"; +import { DragManager } from "../../util/DragManager"; const datepicker = require('js-datepicker'); import * as $ from 'jquery'; import { firebasedynamiclinks } from "googleapis/build/src/apis/firebasedynamiclinks"; @@ -234,6 +235,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro CollectionView={this.props.CollectionView} type={this.props.type} />); + case CollectionViewType.Tree: return ( + <CollectionTreeViewChrome + key="collchrome" + CollectionView={this.props.CollectionView} + type={this.props.type} + />); default: return null; } @@ -280,6 +287,27 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro this.addKeyRestrictions([]); } + private dropDisposer?: DragManager.DragDropDisposer; + protected createDropTarget = (ele: HTMLDivElement) => { + this.dropDisposer && this.dropDisposer(); + if (ele) { + this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + } + } + + @undoBatch + @action + protected drop(e: Event, de: DragManager.DropEvent): boolean { + if (de.data instanceof DragManager.DocumentDragData) { + if (de.data.draggedDocuments.length) { + this.props.CollectionView.props.Document.childLayout = de.data.draggedDocuments[0]; + e.stopPropagation(); + return true; + } + } + return true; + } + render() { let collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled"; return ( @@ -359,6 +387,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro </div> </div> </div> + <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{}}> + TEMPLATE + </div> </div> {this.subChrome()} </div> @@ -536,4 +567,109 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewCh </div > ); } -}
\ No newline at end of file +} + +@observer +export class CollectionTreeViewChrome extends React.Component<CollectionViewChromeProps> { + @observable private _currentKey: string = ""; + @observable private suggestions: string[] = []; + + @computed private get descending() { return BoolCast(this.props.CollectionView.props.Document.stackingHeadersSortDescending); } + @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 toggleSort = () => { this.props.CollectionView.props.Document.stackingHeadersSortDescending = !this.props.CollectionView.props.Document.stackingHeadersSortDescending; }; + @action resetValue = () => { this._currentKey = this.sectionFilter; }; + + render() { + return ( + <div className="collectionTreeViewChrome-cont"> + <button className="collectionTreeViewChrome-sort" onClick={this.toggleSort}> + <div className="collectionTreeViewChrome-sortLabel"> + Sort + </div> + <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}> + <FontAwesomeIcon icon="caret-up" size="2x" color="white" /> + </div> + </button> + <div className="collectionTreeViewChrome-sectionFilter-cont"> + <div className="collectionTreeViewChrome-sectionFilter-label"> + GROUP ITEMS BY: + </div> + <div className="collectionTreeViewChrome-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 + } + }} + oneLine + SetValue={this.setValue} + contents={this.sectionFilter ? this.sectionFilter : "N/A"} + /> + </div> + </div> + </div> + ); + } +} + diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a702ab80e..188a306ec 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, reaction, runInAction, trace } from "mobx"; +import { action, computed, IReactionDisposer, reaction, runInAction, trace, observable } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; @@ -41,6 +41,7 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); +import { IDisposable } from '../../northstar/utils/IDisposable'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -140,6 +141,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu private _hitExpander = false; private _mainCont = React.createRef<HTMLDivElement>(); private _dropDisposer?: DragManager.DragDropDisposer; + _animateToIconDisposer?: IReactionDisposer; + _reactionDisposer?: IReactionDisposer; public get ContentDiv() { return this._mainCont.current; } @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); } @@ -154,8 +157,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu set templates(templates: List<string>) { this.props.Document.templates = templates; } screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); - _animateToIconDisposer?: IReactionDisposer; - _reactionDisposer?: IReactionDisposer; @action componentDidMount() { if (this._mainCont.current) { @@ -207,9 +208,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } @action componentDidUpdate() { - if (this._dropDisposer) { - this._dropDisposer(); - } + this._dropDisposer && this._dropDisposer(); if (this._mainCont.current) { this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } @@ -218,9 +217,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } @action componentWillUnmount() { - if (this._reactionDisposer) this._reactionDisposer(); - if (this._animateToIconDisposer) this._animateToIconDisposer(); - if (this._dropDisposer) this._dropDisposer(); + this._reactionDisposer && this._reactionDisposer(); + this._animateToIconDisposer && this._animateToIconDisposer(); + this._dropDisposer && this._dropDisposer(); DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); } @@ -379,6 +378,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } onPointerDown = (e: React.PointerEvent): void => { + if (e.nativeEvent.cancelBubble) return; this._downX = e.clientX; this._downY = e.clientY; this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0; @@ -727,10 +727,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; } - @computed get brushedDegree() { return Doc.IsBrushedDegree(this.layoutDoc); } render() { - trace(); let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ? this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); @@ -748,7 +746,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu }); } let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined; - let brushDegree = 0;//this.brushedDegree; + let brushDegree = Doc.IsBrushedDegree(this.props.Document); return ( <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 7ef348e9c..a304cb297 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,20 +1,21 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight } from '@fortawesome/free-solid-svg-icons'; -import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx"; +import { action, computed, IReactionDisposer, Lambda, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; -import { Node as ProsNode } from "prosemirror-model"; -import { EditorState, Plugin, Transaction, Selection } from "prosemirror-state"; -import { NodeType, Slice, Node, Fragment } from 'prosemirror-model'; -import { EditorView, NodeView } from "prosemirror-view"; -import { Doc, Opt, DocListCast } from "../../../new_fields/Doc"; -import { Id, Copy } from '../../../new_fields/FieldSymbols'; +import { Fragment, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model"; +import { EditorState, Plugin, Transaction } from "prosemirror-state"; +import { EditorView } from "prosemirror-view"; +import { DateField } from '../../../new_fields/DateField'; +import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc"; +import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; import { RichTextField } from "../../../new_fields/RichTextField"; -import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; -import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; +import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Utils } from '../../../Utils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; @@ -26,17 +27,12 @@ import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { ContextMenu } from "../../views/ContextMenu"; -import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; +import { MainOverlayTextBox } from '../MainOverlayTextBox'; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); -import { DateField } from '../../../new_fields/DateField'; -import { Utils } from '../../../Utils'; -import { MainOverlayTextBox } from '../MainOverlayTextBox'; -import { CheckBox } from '../search/CheckBox'; library.add(faEdit); library.add(faSmile, faTextHeight); @@ -49,6 +45,7 @@ export interface FormattedTextBoxProps { hideOnLeave?: boolean; height?: string; color?: string; + outer_div?: (domminus: HTMLElement) => void; firstinstance?: boolean; } @@ -74,6 +71,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _reactionDisposer: Opt<IReactionDisposer>; private _searchReactionDisposer?: Lambda; private _textReactionDisposer: Opt<IReactionDisposer>; + private _heightReactionDisposer: Opt<IReactionDisposer>; private _proxyReactionDisposer: Opt<IReactionDisposer>; private dropDisposer?: DragManager.DragDropDisposer; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } @@ -164,7 +162,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe dispatchTransaction = (tx: Transaction) => { if (this._editorView) { const state = this._editorView.state.apply(tx); + FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true); this._editorView.updateState(state); + FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = false); if (state.selection.empty && FormattedTextBox._toolTipTextMenu) { const marks = tx.storedMarks; console.log(marks) @@ -251,25 +251,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.props.Document.layout = de.data.draggedDocuments[0]; de.data.draggedDocuments[0].isTemplate = true; e.stopPropagation(); - // let ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc)); - // if (!ldocs) { - // this.dataDoc.subBulletDocs = new List<Doc>([]); - // } - // ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc)); - // if (!ldocs) return; - // if (!ldocs || !ldocs[0] || ldocs[0] instanceof Promise || StrCast((ldocs[0] as Doc).layout).indexOf("CollectionView") === -1) { - // ldocs.splice(0, 0, Docs.StackingDocument([], { title: StrCast(this.dataDoc.title) + "-subBullets", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document.height), width: 300, height: 300 })); - // this.props.addDocument && this.props.addDocument(ldocs[0] as Doc); - // this.props.Document.templates = new List<string>([Templates.Bullet.Layout]); - // this.props.Document.isBullet = true; - // } - // let stackDoc = (ldocs[0] as Doc); - // if (de.data.moveDocument) { - // de.data.moveDocument(de.data.draggedDocuments[0], stackDoc, (doc) => { - // Cast(stackDoc.data, listSpec(Doc))!.push(doc); - // return true; - // }); - // } } } } @@ -311,12 +292,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined; return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`; }, - field2 => { - this._editorView && !this._applyingChange && - this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field2))); - } + field2 => this._editorView && !this._applyingChange && + this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field2))) ); + this.props.isOverlay && (this._heightReactionDisposer = reaction( + () => this.props.Document[WidthSym](), + () => this.tryUpdateHeight() + )); + this._textReactionDisposer = reaction( () => this.extensionDoc, () => { @@ -473,11 +457,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); this._textReactionDisposer && this._textReactionDisposer(); + this._heightReactionDisposer && this._heightReactionDisposer(); this._searchReactionDisposer && this._searchReactionDisposer(); } onPointerDown = (e: React.PointerEvent): void => { - if (this.onClick && e.button === 0) { + if (this.props.onClick && e.button === 0) { e.preventDefault(); } if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { @@ -625,8 +610,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { - if (this.props.isOverlay && this.props.Document.autoHeight) { - let self = this; + if (this.props.Document.autoHeight && this.props.isOverlay) { let xf = this._ref.current!.getBoundingClientRect(); let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height); let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); @@ -665,8 +649,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return ( <div className={`formattedTextBox-cont-${style}`} ref={this._ref} style={{ + overflowY: this.props.Document.autoHeight ? "hidden" : "auto", height: this.props.height ? this.props.height : undefined, - background: this.props.hideOnLeave ? "rgba(0,0,0,0.4)" : undefined, + background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined, opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || Doc.IsBrushed(this.props.Document) ? 1 : 0.1) : 1, color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit", pointerEvents: interactive, diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 564446bb8..bd99a5008 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -1,4 +1,4 @@ -import { observable, action, runInAction } from "mobx"; +import { observable, action, runInAction, ObservableMap } from "mobx"; import { serializable, primitive, map, alias, list, PropSchema, custom } from "serializr"; import { autoObject, SerializationHelper, Deserializable, afterDocDeserialize } from "../client/util/SerializationHelper"; import { DocServer } from "../client/DocServer"; @@ -595,26 +595,23 @@ export namespace Doc { } export class DocBrush { - @observable BrushedDoc: Doc[] = []; + @observable BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap(); } const manager = new DocBrush(); export function IsBrushed(doc: Doc) { - return manager.BrushedDoc.some(d => Doc.AreProtosEqual(d, doc)); + return manager.BrushedDoc.has(doc) || manager.BrushedDoc.has(Doc.GetDataDoc(doc)); } export function IsBrushedDegree(doc: Doc) { - return manager.BrushedDoc.some(d => d === doc) ? 2 : Doc.IsBrushed(doc) ? 1 : 0; + return manager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 2 : manager.BrushedDoc.has(doc) ? 1 : 0; } export function BrushDoc(doc: Doc) { - if (manager.BrushedDoc.indexOf(doc) === -1) runInAction(() => manager.BrushedDoc.push(doc)); + manager.BrushedDoc.set(doc, true); + manager.BrushedDoc.set(Doc.GetDataDoc(doc), true); } export function UnBrushDoc(doc: Doc) { - let index = manager.BrushedDoc.indexOf(doc); - if (index !== -1) runInAction(() => manager.BrushedDoc.splice(index, 1)); + manager.BrushedDoc.delete(doc); + manager.BrushedDoc.delete(Doc.GetDataDoc(doc)); } } -Scripting.addGlobal(function renameAlias(doc: any, n: any) { - return StrCast(doc.title).replace(/\([0-9]*\)/, "") + `(${n})`; -}); -Scripting.addGlobal(function getProto(doc: any) { - return Doc.GetProto(doc); -});
\ No newline at end of file +Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(doc.title).replace(/\([0-9]*\)/, "") + `(${n})`; }); +Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });
\ No newline at end of file diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts index 23605cfb0..7494c9bd1 100644 --- a/src/new_fields/SchemaHeaderField.ts +++ b/src/new_fields/SchemaHeaderField.ts @@ -1,8 +1,8 @@ import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, createSimpleSchema, primitive } from "serializr"; +import { serializable, primitive } from "serializr"; import { ObjectField } from "./ObjectField"; import { Copy, ToScriptString, OnUpdate } from "./FieldSymbols"; -import { scriptingGlobal, Scripting } from "../client/util/Scripting"; +import { scriptingGlobal } from "../client/util/Scripting"; import { ColumnType } from "../client/views/collections/CollectionSchemaView"; export const PastelSchemaPalette = new Map<string, string>([ @@ -53,9 +53,11 @@ export class SchemaHeaderField extends ObjectField { @serializable(primitive()) width: number; @serializable(primitive()) + collapsed: boolean | undefined; + @serializable(primitive()) desc: boolean | undefined; // boolean determines sort order, undefined when no sort - constructor(heading: string = "", color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean) { + constructor(heading: string = "", color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean, collapsed?: boolean) { super(); this.heading = heading; @@ -63,6 +65,7 @@ export class SchemaHeaderField extends ObjectField { this.type = type ? type : 0; this.width = width ? width : -1; this.desc = desc; + this.collapsed = collapsed; } setHeading(heading: string) { @@ -90,6 +93,11 @@ export class SchemaHeaderField extends ObjectField { this[OnUpdate](); } + setCollapsed(collapsed: boolean | undefined) { + this.collapsed = collapsed; + this[OnUpdate](); + } + [Copy]() { return new SchemaHeaderField(this.heading, this.color, this.type); } diff --git a/src/server/index.ts b/src/server/index.ts index eae018f13..7a425b60a 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -436,7 +436,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) { console.log(pageNumber); pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { console.log("reading " + page); - let viewport = page.getViewport(1); + let viewport = page.getViewport({ scale: 1 }); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, |