diff options
author | usodhi <61431818+usodhi@users.noreply.github.com> | 2021-03-17 21:56:41 -0400 |
---|---|---|
committer | usodhi <61431818+usodhi@users.noreply.github.com> | 2021-03-17 21:56:41 -0400 |
commit | c4ca83acf90676abf2f822b4b0ff455fe50c0ddb (patch) | |
tree | 64f24440a4a6d58ac6998274c84a3e3b3db9234e | |
parent | 9c612838c775a6fa9737963618aeebbc427e794c (diff) | |
parent | 6f4f0ffb9f4ab816cf6055c62afc6f79b8e4961f (diff) |
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into filters
21 files changed, 267 insertions, 276 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c13a69402..0d2f04569 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -778,8 +778,6 @@ export namespace Docs { LinkManager.Instance.addLink(doc); - source.doc.links === undefined && (Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)")); - target.doc.links === undefined && (Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)")); return doc; } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 31c70427c..88bb1207f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1157,8 +1157,8 @@ export class CurrentUserUtils { input.click(); } - public static snapshotDashboard = (userDoc: Doc) => { - const copy = CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); + public static async snapshotDashboard(userDoc: Doc) { + const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy); CurrentUserUtils.openDashboard(userDoc, copy); } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 34cdb50e3..966abc0e7 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -39,7 +39,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined; inkDoc.color = "#9b9b9bff"; inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined; - }) + }); public _prevX = 0; public _prevY = 0; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 3dfff8c87..9dba15f55 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -156,7 +156,7 @@ export class MainView extends React.Component { fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faChevronLeft, fa.faChevronDown, fa.faChevronUp, fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt, fa.faFileAudio, fa.faFileVideo, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer, - fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote, + fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote, fa.faArrowsAltV, fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes, fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined, fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript, diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index fbcd55c47..7a83295c7 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -79,6 +79,12 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get titleButton() { return this.propertyToggleBtn("Title", "_showTitle", on => "Switch between title styles", on => "text-width", (dv, doc) => (dv?.rootDoc || doc)._showTitle = !(dv?.rootDoc || doc)._showTitle ? "title" : (dv?.rootDoc || doc)._showTitle === "title" ? "title:hover" : undefined); } + @computed get autoHeightButton() { + return this.propertyToggleBtn("Auto\xA0Size", "_autoHeight", on => `Automatical vertical sizing to show all content`, on => "arrows-alt-v"); + } + @computed get gridButton() { + return this.propertyToggleBtn("Grid", "_backgroundGrid-show", on => `Display background grid in collection`, on => "border-all"); + } @computed get onClickButton() { @@ -95,6 +101,27 @@ export class PropertiesButtons extends React.Component<{}, {}> { </div> </Tooltip>; } + @computed + get perspectiveButton() { + return !this.selectedDoc ? (null) : <Tooltip title={<div className="dash-tooltip">Choose view perspective</div>} placement="top"> + <div> + <div className="propertiesButtons-linkFlyout"> + <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.onPerspectiveFlyout}> + <div className={"propertiesButtons-linkButton-empty"} onPointerDown={e => e.stopPropagation()} > + <FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="lg" /> + </div> + </Flyout> + </div> + <div className="propertiesButtons-title"> Perspective </div> + </div> + </Tooltip>; + } + + @undoBatch + handlePerspectiveChange = (e: any) => { + this.selectedDoc && (this.selectedDoc._viewType = e.target.value); + SelectionManager.Views().filter(dv => dv.docView).map(dv => dv.docView!).forEach(docView => docView.layoutDoc._viewType = e.target.value); + } @undoBatch @action @@ -136,12 +163,28 @@ export class PropertiesButtons extends React.Component<{}, {}> { {Doc.UserDoc().noviceMode ? (null) : <div onPointerDown={this.editOnClickScript} className="onClickFlyout-editScript"> Edit onClick Script</div>} </div>; } + @computed + get onPerspectiveFlyout() { + const excludedViewTypes = Doc.UserDoc().noviceMode ? [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Stacking, CollectionViewType.Map, CollectionViewType.Linear] : + [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear]; + + const makeLabel = (value: string, label: string) => <div className="radio"> + <label> + <input type="radio" value={value} checked={(this.selectedDoc?._viewType ?? "invalid") === value} onChange={this.handlePerspectiveChange} /> + {label} + </label> + </div>; + return <form> + {Object.values(CollectionViewType).filter(type => !excludedViewTypes.includes(type)).map(type => makeLabel(type, type))} + </form>; + } render() { const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)]; const isText = layoutField instanceof RichTextField; const isInk = layoutField instanceof InkField; const isCollection = this.selectedDoc?.type === DocumentType.COL; + const isStacking = this.selectedDoc?._viewType === CollectionViewType.Stacking; const isFreeForm = this.selectedDoc?._viewType === CollectionViewType.Freeform; const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => <div className="propertiesButtons-button" style={style}> {ele} </div>; @@ -149,15 +192,18 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons"> {toggle(this.titleButton)} {toggle(this.captionButton)} - {toggle(this.chromeButton, { display: isCollection ? "" : "none" })} {toggle(this.lockButton)} {toggle(this.dictationButton)} {toggle(this.onClickButton)} - {toggle(this.clustersButton, { display: !isFreeForm ? "none" : "" })} - {toggle(this.panButton, { display: !isFreeForm ? "none" : "" })} - {toggle(this.fitContentButton, { display: !isFreeForm && !isText ? "none" : "" })} {toggle(this.fitWidthButton)} + {toggle(this.fitContentButton, { display: !isFreeForm ? "none" : "" })} + {toggle(this.autoHeightButton, { display: !isText && !isStacking ? "none" : "" })} {toggle(this.maskButton, { display: !isInk ? "none" : "" })} + {toggle(this.chromeButton, { display: isCollection ? "" : "none" })} + {toggle(this.gridButton, { display: isCollection ? "" : "none" })} + {toggle(this.clustersButton, { display: !isFreeForm ? "none" : "" })} + {toggle(this.panButton, { display: !isFreeForm ? "none" : "" })} + {toggle(this.perspectiveButton, { display: !isCollection ? "none" : "" })} </div>; } }
\ No newline at end of file diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 9f04e8a6e..23d8baeaf 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -45,7 +45,7 @@ class OtherToggle extends React.Component<{ checked: boolean, name: string, togg export interface TemplateMenuProps { docViews: DocumentView[]; - templates: Map<string, boolean>; + templates?: Map<string, boolean>; } @@ -133,6 +133,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { ContainingCollectionView={undefined} styleProvider={DefaultStyleProvider} layerProvider={undefined} + setHeight={returnFalse} docViewPath={returnEmptyDoclist} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index b9757dde3..57f4555a1 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -342,25 +342,27 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { } } - public static Copy(doc: Doc) { + public static async Copy(doc: Doc, clone = false) { + clone = !Doc.UserDoc().noviceMode; let json = StrCast(doc.dockingConfig); + if (clone) { + const cloned = (await Doc.MakeClone(doc)); + Array.from(cloned.map.entries()).map(entry => json = json.replace(entry[0], entry[1][Id])); + Doc.SetInPlace(cloned.clone, "dockingConfig", json, true); + return cloned.clone; + } const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); - const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) || []; - const docs = docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc); - const newtabs = docs.map(doc => { - const copy = Doc.MakeAlias(doc); - json = json.replace(doc[Id], copy[Id]); - return copy; + const origtabids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) || []; + const origtabs = origtabids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc); + const newtabs = origtabs.map(origtab => { + const origtabdocs = DocListCast(origtab.data); + const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true) : Doc.MakeAlias(origtab); + const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeAlias(origtabdoc)); + newtabdocs.length && Doc.SetInPlace(newtab, "data", new List<Doc>(newtabdocs), true); + json = json.replace(origtab[Id], newtab[Id]); + return newtab; }); - const copy = Docs.Create.DockDocument(newtabs, json, { title: "Snapshot: " + doc.title }); - const docsublists = DocListCast(doc.data); - const copysublists = DocListCast(copy.data); - const docother = Cast(docsublists[1], Doc, null); - const copyother = Cast(copysublists[1], Doc, null); - const newother = DocListCast(docother.data).map(doc => Doc.MakeAlias(doc)); - Doc.GetProto(copyother).data = new List<Doc>(newother); - - return copy; + return Docs.Create.DockDocument(newtabs, json, { title: "Snapshot: " + doc.title }); } @action diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 46bfd841e..581520619 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -25,10 +25,13 @@ export const Flyout = higflyout.default; interface CMVFieldRowProps { rows: () => number; headings: () => object[]; + Document: Doc; + chromeStatus: string; heading: string; headingObject: SchemaHeaderField | undefined; docList: Doc[]; parent: CollectionStackingView; + pivotField: string; type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined; createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; @@ -90,7 +93,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr if (de.complete.docDragData) { (this.props.parent.Document.dropConverter instanceof ScriptField) && this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData }); - const key = StrCast(this.props.parent.props.Document._pivotField); + const key = this.props.pivotField; const castedValue = this.getValue(this.heading); const onLayoutDoc = this.onLayoutDoc(key); de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc)); @@ -110,7 +113,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr @action headingChanged = (value: string, shiftDown?: boolean) => { this._createAliasSelected = false; - const key = StrCast(this.props.parent.props.Document._pivotField); + const key = this.props.pivotField; const castedValue = this.getValue(value); if (castedValue) { if (this.props.parent.columnHeaders) { @@ -143,7 +146,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { if (!value && !forceEmptyNote) return false; this._createAliasSelected = false; - const key = StrCast(this.props.parent.props.Document._pivotField); + const key = this.props.pivotField; const newDoc = Docs.Create.TextDocument("", { _autoHeight: true, _width: 200, _fitWidth: true, title: value }); const onLayoutDoc = this.onLayoutDoc(key); FormattedTextBox.SelectOnLoad = newDoc[Id]; @@ -155,7 +158,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr deleteRow = undoBatch(action(() => { this._createAliasSelected = false; - const key = StrCast(this.props.parent.props.Document._pivotField); + const key = this.props.pivotField; this.props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true)); if (this.props.parent.columnHeaders && this.props.headingObject) { const index = this.props.parent.columnHeaders.indexOf(this.props.headingObject); @@ -171,8 +174,8 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr } headerMove = (e: PointerEvent) => { - const alias = Doc.MakeAlias(this.props.parent.props.Document); - const key = StrCast(this.props.parent.props.Document._pivotField); + const alias = Doc.MakeAlias(this.props.Document); + const key = this.props.pivotField; let value = this.getValue(this.heading); value = typeof value === "string" ? `"${value}"` : value; const script = `return doc.${key} === ${value}`; @@ -187,7 +190,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr @action headerDown = (e: React.PointerEvent<HTMLDivElement>) => { if (e.button === 0 && !e.ctrlKey) { - setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this.props.parent.props.Document._chromeStatus && this.collapseSection(e)); + setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this.props.chromeStatus && this.collapseSection(e)); this._createAliasSelected = false; } } @@ -251,8 +254,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr @computed get contentLayout() { const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap)))); - const style = this.props.parent; - const chromeStatus = this.props.parent.props.Document._chromeStatus; + const chromeStatus = this.props.chromeStatus; const showChrome = (chromeStatus !== 'view-mode' && chromeStatus); const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `; return this.collapsed ? (null) : @@ -286,8 +288,8 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr } @computed get headingView() { - const noChrome = !this.props.parent.props.Document._chromeStatus; - const key = StrCast(this.props.parent.props.Document._pivotField); + const noChrome = !this.props.chromeStatus; + const key = this.props.pivotField; const evContents = this.heading ? this.heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`; const editableHeaderView = <EditableView GetValue={() => evContents} @@ -295,7 +297,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr contents={evContents} oneLine={true} toggle={this.toggleVisibility} />; - return this.props.parent.props.Document.miniHeaders ? + return this.props.Document.miniHeaders ? <div className="collectionStackingView-miniHeader"> {editableHeaderView} </div> : diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss index 47adb6a1c..2c81d727e 100644 --- a/src/client/views/collections/CollectionMenu.scss +++ b/src/client/views/collections/CollectionMenu.scss @@ -318,7 +318,6 @@ .collectionMenu-webUrlButtons { margin-left: 44; background: lightGray; - width: 100%; display: flex; } diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 51a67ea7d..6a9ad27c0 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -223,7 +223,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp case CollectionViewType.Carousel3D: return this._freeform_commands; } } - private _picker: any; private _commandRef = React.createRef<HTMLInputElement>(); private _viewRef = React.createRef<HTMLInputElement>(); @observable private _currentKey: string = ""; @@ -342,8 +341,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp } @computed get viewModes() { - const excludedViewTypes = Doc.UserDoc().noviceMode ? [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.Map, CollectionViewType.Linear, CollectionViewType.Time] : - [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.Linear]; + const excludedViewTypes = Doc.UserDoc().noviceMode ? [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Stacking, CollectionViewType.Map, CollectionViewType.Linear] : + [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear]; const isPres: boolean = (this.document && this.document.type === DocumentType.PRES); return isPres ? (null) : (<div className="collectionViewBaseChrome-viewModes" > <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom"> @@ -430,14 +429,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp @computed get pinWithViewButton() { const presPinWithViewIcon = <img src={`/assets/pinWithView.png`} style={{ margin: "auto", width: 19 }} />; - const targetDoc = this.selectedDoc; - {/* return (!targetDoc || (targetDoc._viewType !== CollectionViewType.Freeform && targetDoc.type !== DocumentType.IMG)) ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin to presentation trail with current view"}</div></>} placement="top"> */ } - return (targetDoc && targetDoc.type !== DocumentType.PRES && (targetDoc._viewType === CollectionViewType.Freeform || targetDoc._viewType === CollectionViewType.Stacking || targetDoc.type === DocumentType.VID || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.VID || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.COMPARISON)) ? <Tooltip title={<><div className="dash-tooltip">{"Pin with current view"}</div></>} placement="top"> - <button className="antimodeMenu-button" style={{ borderRight: "1px solid gray", borderLeft: "1px solid gray", justifyContent: 'center' }} - onClick={() => this.pinWithView(targetDoc)}> - {presPinWithViewIcon} - </button> - </Tooltip> : (null); + return !this.selectedDoc ? (null) : + <Tooltip title={<div className="dash-tooltip">{"Pin with current view"}</div>} placement="top"> + <button className="antimodeMenu-button" style={{ justifyContent: 'center' }} + onClick={() => this.pinWithView(this.selectedDoc)}> + {presPinWithViewIcon} + </button> + </Tooltip>; } @@ -488,48 +486,40 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp @computed get lightboxButton() { const targetDoc = this.selectedDoc; - return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Lightbox of Documents"}</div>} placement="top"> - <button className="antimodeMenu-button" onPointerDown={() => { + return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"View in Lightbox"}</div>} placement="top"> + <button className="antimodeMenu-button" style={{ borderRight: "1px solid gray", justifyContent: 'center' }} onPointerDown={() => { const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); - if (docs.length) { - LightboxView.SetLightboxDoc(targetDoc, undefined, docs); - } + LightboxView.SetLightboxDoc(targetDoc, undefined, docs); }}> <FontAwesomeIcon className="documentdecorations-icon" icon="desktop" size="lg" /> </button> </Tooltip>; } - @computed get gridbackgroundButton() { - const targetDoc = this.selectedDoc; - return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Toggle background grid"}</div>} placement="top"> - <button className="antimodeMenu-button" onPointerDown={action(() => targetDoc["_backgroundGrid-show"] = !targetDoc["_backgroundGrid-show"])}> - <FontAwesomeIcon className="documentdecorations-icon" icon={targetDoc["_backgroundGrid-show"] ? "border-all" : ["far", "square"]} size="lg" /> - </button> - </Tooltip>; - } render() { return ( <div className="collectionMenu-cont" > <div className="collectionMenu"> <div className="collectionViewBaseChrome"> - {this.notACollection || this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes} - {!this._buttonizableCommands ? (null) : this.templateChrome} - {this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? (null) : - <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom"> - <button className={"antimodeMenu-button"} key="float" - style={{ backgroundColor: this.props.docView.layoutDoc.z ? "121212" : undefined, borderRight: "1px solid gray" }} - onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}> - <FontAwesomeIcon icon={["fab", "buffer"]} size={"lg"} /> - </button> - </Tooltip>} - {this.notACollection ? (null) : this.lightboxButton} - {this.notACollection ? (null) : this.gridbackgroundButton} {this.aliasButton} {/* {this.pinButton} */} {this.pinWithViewButton} + {this.lightboxButton} + <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom"> + <button className={"antimodeMenu-button"} key="float" + style={{ + backgroundColor: this.props.docView.layoutDoc.z ? "121212" : undefined, borderRight: "1px solid gray", + pointerEvents: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? "none" : undefined, + color: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? "dimgrey" : undefined + }} + onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}> + <FontAwesomeIcon icon={["fab", "buffer"]} size={"lg"} /> + </button> + </Tooltip> + {this.subChrome} + {/* {this.notACollection || this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes} */} + {!this._buttonizableCommands ? (null) : this.templateChrome} </div> - {this.subChrome} </div> </div> ); @@ -737,38 +727,36 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu render() { return !this.props.docView.layoutDoc ? (null) : <div className="collectionFreeFormMenu-cont"> - {!Doc.UserDoc().noviceMode && !this.isText && !this.props.isDoc ? - <> - <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom"> - <div className="backKeyframe" onClick={this.prevKeyframe}> - <FontAwesomeIcon icon={"caret-left"} size={"lg"} /> - </div> - </Tooltip> - <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom"> - <div className="numKeyframe" style={{ color: this.document.editing ? "white" : "black", backgroundColor: this.document.editing ? "#5B9FDD" : "#AEDDF8" }} - onClick={action(() => this.document.editing = !this.document.editing)} > - {NumCast(this.document._currentFrame)} - </div> - </Tooltip> - <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom"> - <div className="fwdKeyframe" onClick={this.nextKeyframe}> - <FontAwesomeIcon icon={"caret-right"} size={"lg"} /> - </div> - </Tooltip> - </> - : null} - {!this.selectedDocumentView?.ComponentView?.menuControls ? (null) : this.selectedDocumentView?.ComponentView?.menuControls?.()} {!this.isText ? <> {this.drawButtons} {this.widthPicker} {this.colorPicker} {this.fillPicker} + {Doc.UserDoc().noviceMode || this.props.isDoc ? (null) : + <> + <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom"> + <div className="backKeyframe" onClick={this.prevKeyframe}> + <FontAwesomeIcon icon={"caret-left"} size={"lg"} /> + </div> + </Tooltip> + <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom"> + <div className="numKeyframe" style={{ color: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? "white" : "black", backgroundColor: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? "#5B9FDD" : "#AEDDF8" }} + onClick={action(() => this.props.docView.ComponentView?.setKeyFrameEditing?.(!this.props.docView.ComponentView?.getKeyFrameEditing?.()))} > + {NumCast(this.document._currentFrame)} + </div> + </Tooltip> + <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom"> + <div className="fwdKeyframe" onClick={this.nextKeyframe}> + <FontAwesomeIcon icon={"caret-right"} size={"lg"} /> + </div> + </Tooltip> + </>} </> : - (null) + <RichTextMenu /> } - {<div style={{ display: !this.isText ? "none" : undefined }}><RichTextMenu /></div>} + {!this.selectedDocumentView?.ComponentView?.menuControls ? (null) : this.selectedDocumentView?.ComponentView?.menuControls?.()} </div>; } } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index e3d7118e9..3f821bbcc 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -54,7 +54,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, @observable _cursor: CursorProperty = "grab"; @observable _scroll = 0; // used to force the document decoration to update when scrolling @computed get chromeStatus() { return this.props.chromeStatus || StrCast(this.layoutDoc._chromeStatus); } - @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField)); } + @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); } @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); } @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); } @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } @@ -65,7 +65,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; } @computed get showAddAGroup() { return (this.pivotField && (this.chromeStatus !== 'view-mode' && this.chromeStatus)); } @computed get columnWidth() { - return Math.min(this.props.PanelWidth() /* / NumCast(this.layoutDoc._viewScale, 1)*/ - 2 * this.xMargin, + return Math.min(this.props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250)); } @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; } @@ -78,7 +78,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, } } - children(docs: Doc[], columns?: number) { + children = (docs: Doc[]) => { TraceMobx(); this._docXfs.length = 0; return docs.map((d, i) => { @@ -145,7 +145,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, () => this.layoutDoc._columnHeaders = new List() ); this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, - () => this.props.setHeight(this.headerMargin + this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0))); + () => this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), + this.headerMargin + (this.isStackingView ? + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))) : + this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0))))); } componentWillUnmount() { @@ -389,8 +392,6 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, type = types[0]; } } - const 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 unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)} observeHeight={ref => { @@ -401,22 +402,31 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), - Math.max(...this.refList.map(r => NumCast(Doc.Layout(doc)._viewScale, 1) * Number(getComputedStyle(r).height.replace("px", ""))))); + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))); if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { - this.props.setHeight(height * NumCast(Doc.Layout(doc)._viewScale, 1)); + this.props.setHeight(height); } } })); this.observer.observe(ref); } }} + addDocument={this.addDocument} + chromeStatus={this.chromeStatus} + columnHeaders={this.columnHeaders} + Document={this.props.Document} + DataDoc={this.props.DataDoc} + renderChildren={this.children} + columnWidth={this.columnWidth} + numGroupColumns={this.numGroupColumns} + gridGap={this.gridGap} + pivotField={this.pivotField} key={heading?.heading ?? ""} - cols={cols} headings={this.headings} heading={heading?.heading ?? ""} headingObject={heading} docList={docList} - parent={this} + yMargin={this.yMargin} type={type} createDropTarget={this.createDashEventsTarget} screenToLocalTransform={this.props.ScreenToLocalTransform} @@ -434,11 +444,13 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); return <CollectionMasonryViewFieldRow showHandle={first} + Document={this.props.Document} + chromeStatus={this.chromeStatus} + pivotField={this.pivotField} unobserveHeight={(ref) => this.refList.splice(this.refList.indexOf(ref), 1)} observeHeight={(ref) => { if (ref) { this.refList.push(ref); - const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; this.observer = new _global.ResizeObserver(action((entries: any) => { if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 389b449b5..70ec1f925 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -2,9 +2,8 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../fields/Doc"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { RichTextField } from "../../../fields/RichTextField"; -import { listSpec } from "../../../fields/Schema"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { ScriptField } from "../../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; @@ -20,7 +19,6 @@ import { undoBatch } from "../../util/UndoManager"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { EditableView } from "../EditableView"; -import { CollectionStackingView } from "./CollectionStackingView"; import "./CollectionStackingView.scss"; import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; import { Id } from "../../../fields/FieldSymbols"; @@ -29,13 +27,22 @@ export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; interface CSVFieldColumnProps { - cols: () => number; - headings: () => object[]; + Document: Doc; + DataDoc: Opt<Doc>; + docList: Doc[]; heading: string; + pivotField: string; + chromeStatus: string; + columnHeaders: SchemaHeaderField[] | undefined; headingObject: SchemaHeaderField | undefined; - docList: Doc[]; - parent: CollectionStackingView; + yMargin: number; + columnWidth: number; + numGroupColumns: number; + gridGap: number; type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined; + headings: () => object[]; + renderChildren: (docs: Doc[]) => JSX.Element[]; + addDocument: (doc: Doc | Doc[]) => boolean; createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; observeHeight: (myref: any) => void; @@ -68,39 +75,25 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @undoBatch columnDrop = action((e: Event, de: DragManager.DropEvent) => { - if (de.complete.docDragData) { - const key = StrCast(this.props.parent.props.Document._pivotField); - const castedValue = this.getValue(this._heading); - de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, false)); - this.props.parent.onInternalDrop(e, de); - e.stopPropagation(); - } + const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) }; + drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false)); }); getValue = (value: string): any => { const parsed = parseInt(value); - if (!isNaN(parsed)) { - return parsed; - } - if (value.toLowerCase().indexOf("true") > -1) { - return true; - } - if (value.toLowerCase().indexOf("false") > -1) { - return false; - } + 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) => { - const key = StrCast(this.props.parent.props.Document._pivotField); const castedValue = this.getValue(value); if (castedValue) { - if (this.props.parent.columnHeaders) { - if (this.props.parent.columnHeaders.map(i => i.heading).indexOf(castedValue.toString()) > -1) { - return false; - } + if (this.props.columnHeaders?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) { + return false; } - this.props.docList.forEach(d => d[key] = castedValue); + this.props.docList.forEach(d => d[this.props.pivotField] = castedValue); if (this.props.headingObject) { this.props.headingObject.setHeading(castedValue.toString()); this._heading = this.props.headingObject.heading; @@ -112,33 +105,18 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @action changeColumnColor = (color: string) => { - if (this.props.headingObject) { - this.props.headingObject.setColor(color); - this._color = color; - } - } - - @action - pointerEntered = () => { - if (SnappingManager.GetIsDragging()) { - this._background = "#b4b4b4"; - } - } - - @action - pointerLeave = () => { - this._background = "inherit"; + this.props.headingObject?.setColor(color); + this._color = color; } - @action - textCallback = (char: string) => { - return this.addDocument("", false, true); - } + @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4"); + @action pointerLeave = () => this._background = "inherit"; + textCallback = (char: string) => this.addNewTextDoc("", false, true); @action - addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { + addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { if (!value && !forceEmptyNote) return false; - const key = StrCast(this.props.parent.props.Document._pivotField); + const key = this.props.pivotField; const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _fitWidth: true, title: value, _autoHeight: true }); newDoc[key] = this.getValue(this.props.heading); const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); @@ -146,39 +124,33 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC newDoc.heading = heading; FormattedTextBox.SelectOnLoad = newDoc[Id]; FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " "; - return this.props.parent.addDocument?.(newDoc) || false; + return this.props.addDocument?.(newDoc) || false; } @action deleteColumn = () => { - const key = StrCast(this.props.parent.props.Document._pivotField); - this.props.docList.forEach(d => d[key] = undefined); - if (this.props.parent.columnHeaders && this.props.headingObject) { - const index = this.props.parent.columnHeaders.indexOf(this.props.headingObject); - this.props.parent.columnHeaders.splice(index, 1); + this.props.docList.forEach(d => d[this.props.pivotField] = undefined); + if (this.props.columnHeaders && this.props.headingObject) { + const index = this.props.columnHeaders.indexOf(this.props.headingObject); + this.props.columnHeaders.splice(index, 1); } } @action collapseSection = () => { - if (this.props.headingObject) { - this.props.headingObject.setCollapsed(!this.props.headingObject.collapsed); - this.toggleVisibility(); - } + this.props.headingObject?.setCollapsed(!this.props.headingObject.collapsed); + this.toggleVisibility(); } - headerDown = (e: React.PointerEvent<HTMLDivElement>) => { - setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); - } + headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); startDrag = (e: PointerEvent, down: number[], delta: number[]) => { - const alias = Doc.MakeAlias(this.props.parent.props.Document); - alias._width = this.props.parent.props.PanelWidth() / (Cast(this.props.parent.columnHeaders, listSpec(SchemaHeaderField))?.length || 1); + const alias = Doc.MakeAlias(this.props.Document); + alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1); alias._pivotField = undefined; - const key = StrCast(this.props.parent.props.Document._pivotField); let value = this.getValue(this._heading); value = typeof value === "string" ? `"${value}"` : value; - alias.viewSpecScript = ScriptField.MakeFunction(`doc.${key} === ${value}`, { doc: Doc.name }); + alias.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name }); if (alias.viewSpecScript) { DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY); return true; @@ -187,43 +159,25 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC } renderColorPicker = () => { - const selected = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; - - const pink = PastelSchemaPalette.get("pink2"); - const purple = PastelSchemaPalette.get("purple4"); - const blue = PastelSchemaPalette.get("bluegreen1"); - const yellow = PastelSchemaPalette.get("yellow4"); - const red = PastelSchemaPalette.get("red2"); - const green = PastelSchemaPalette.get("bluegreen7"); - const cyan = PastelSchemaPalette.get("bluegreen5"); - const orange = PastelSchemaPalette.get("orange1"); const gray = "#f1efeb"; - - return ( - <div className="collectionStackingView-colorPicker"> - <div className="colorOptions"> - <div className={"colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)}></div> - <div className={"colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)}></div> - <div className={"colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)}></div> - <div className={"colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)}></div> - <div className={"colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)}></div> - <div className={"colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)}></div> - <div className={"colorPicker" + (selected === green ? " active" : "")} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)}></div> - <div className={"colorPicker" + (selected === cyan ? " active" : "")} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)}></div> - <div className={"colorPicker" + (selected === orange ? " active" : "")} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)}></div> - </div> + const selected = this.props.headingObject ? this.props.headingObject.color : gray; + const colors = ["pink2", "purple4", "bluegreen1", "yellow4", "gray", "red2", "bluegreen7", "bluegreen5", "orange1"]; + return <div className="collectionStackingView-colorPicker"> + <div className="colorOptions"> + {colors.map(col => { + const palette = PastelSchemaPalette.get(col); + return <div className={"colorPicker" + (selected === palette ? " active" : "")} style={{ backgroundColor: palette }} onClick={() => this.changeColumnColor(palette!)} /> + })} </div> - ); + </div>; } renderMenu = () => { - return ( - <div className="collectionStackingView-optionPicker"> - <div className="optionOptions"> - <div className={"optionPicker" + (true ? " active" : "")} onClick={action(() => { })}>Add options here</div> - </div> - </div > - ); + return <div className="collectionStackingView-optionPicker"> + <div className="optionOptions"> + <div className={"optionPicker" + (true ? " active" : "")} onClick={action(() => { })}>Add options here</div> + </div> + </div >; } @observable private collapsed: boolean = false; @@ -234,22 +188,22 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC ContextMenu.Instance.clearItems(); const layoutItems: ContextMenuProps[] = []; const docItems: ContextMenuProps[] = []; - const dataDoc = this.props.parent.props.DataDoc || this.props.parent.Document; + const dataDoc = this.props.DataDoc || this.props.Document; DocUtils.addDocumentCreatorMenuItems((doc) => { FormattedTextBox.SelectOnLoad = doc[Id]; - return this.props.parent.props.addDocument?.(doc) || false; - }, this.props.parent.props.addDocument || returnFalse, x, y, true); + return this.props.addDocument?.(doc); + }, this.props.addDocument, x, y, true); Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey => docItems.push({ description: ":" + fieldKey, event: () => { - const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.parent.props.Document)); + const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document)); if (created) { - if (this.props.parent.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document); + if (this.props.Document.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, this.props.Document); } - return this.props.parent.props.addDocument?.(created) || false; + return this.props.addDocument?.(created); } }, icon: "compress-arrows-alt" })); @@ -258,25 +212,25 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC description: ":" + fieldKey, event: () => { const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey }); if (created) { - const container = this.props.parent.Document.resolvedDataDoc ? Doc.GetProto(this.props.parent.Document) : this.props.parent.Document; + const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document; if (container.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, container); return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); } - return this.props.parent.props.addDocument?.(created) || false; + return this.props.addDocument?.(created) || false; } }, icon: "compress-arrows-alt" })); !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" }); !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" }); ContextMenu.Instance.setDefaultItem("::", (name: string): void => { - Doc.GetProto(this.props.parent.props.Document)[name] = ""; + Doc.GetProto(this.props.Document)[name] = ""; const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true }); if (created) { - if (this.props.parent.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document); + if (this.props.Document.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, this.props.Document); } - this.props.parent.props.addDocument?.(created); + this.props.addDocument?.(created); } }); const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y); @@ -284,23 +238,19 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC } @computed get innards() { TraceMobx(); - const cols = this.props.cols(); - const key = StrCast(this.props.parent.props.Document._pivotField); - let templatecols = ""; + const key = this.props.pivotField; const headings = this.props.headings(); const heading = this._heading; - const style = this.props.parent; - const singleColumn = style.isStackingView; - const columnYMargin = this.props.headingObject ? 0 : NumCast(this.props.parent.yMargin, 5); + const columnYMargin = this.props.headingObject ? 0 : this.props.yMargin; const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); const evContents = heading ? heading : this.props?.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`; const headingView = this.props.headingObject ? <div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef} style={{ - marginTop: NumCast(this.props.parent.yMargin, 5), - width: (style.columnWidth) / + marginTop: this.props.yMargin, + width: (this.props.columnWidth) / ((uniqueHeadings.length + - ((this.props.parent.chromeStatus !== 'view-mode' && this.props.parent.chromeStatus) ? 1 : 0)) || 1) + ((this.props.chromeStatus !== 'view-mode' && this.props.chromeStatus) ? 1 : 0)) || 1) }}> <div className={"collectionStackingView-collapseBar" + (this.props.headingObject.collapsed === true ? " active" : "")} onClick={this.collapseSection}></div> {/* the default bucket (no key value) has a tooltip that describes what it is. @@ -337,34 +287,32 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC } </div> </div> : (null); - for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `; - const chromeStatus = this.props.parent.chromeStatus; - const type = this.props.parent.props.Document.type; + const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `; + const type = this.props.Document.type; return <> - {this.props.parent.Document._columnsHideIfEmpty ? (null) : headingView} + {this.props.Document._columnsHideIfEmpty ? (null) : headingView} { this.collapsed ? (null) : <div> - <div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`} + <div key={`${heading}-stack`} className={`collectionStackingView-masonrySingle`} style={{ - padding: singleColumn ? `${columnYMargin}px ${0}px ${style.yMargin}px ${0}px` : `${columnYMargin}px ${0}px`, + padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`, margin: "auto", width: "max-content", //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" + gridGap: this.props.gridGap, + gridTemplateColumns: templatecols, + gridAutoRows: "0px" }}> - {this.props.parent.children(this.props.docList, uniqueHeadings.length)} - {singleColumn ? (null) : this.props.parent.columnDragger} + {this.props.renderChildren(this.props.docList)} </div> - {(chromeStatus !== 'view-mode' && chromeStatus && type !== DocumentType.PRES) ? + {(this.props.chromeStatus !== 'view-mode' && this.props.chromeStatus && type !== DocumentType.PRES) ? <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" - style={{ width: style.columnWidth / style.numGroupColumns, marginBottom: 10 }}> + style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}> <EditableView GetValue={returnEmptyString} - SetValue={this.addDocument} + SetValue={this.addNewTextDoc} textCallback={this.textCallback} contents={"+ NEW"} toggle={this.toggleVisibility} @@ -381,11 +329,10 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC const headings = this.props.headings(); const heading = this._heading; const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); - const chromeStatus = this.props.parent.props.Document._chromeStatus; return ( <div className={"collectionStackingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading} style={{ - width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus) ? 1 : 0)) || 1)}%`, + width: `${100 / ((uniqueHeadings.length + ((this.props.chromeStatus !== 'view-mode' && this.props.chromeStatus) ? 1 : 0)) || 1)}%`, height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined, background: this._background }} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0ee2fad2e..57dba0f75 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -110,6 +110,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @observable _clusterSets: (Doc[])[] = []; @observable _timelineRef = React.createRef<Timeline>(); @observable _marqueeRef = React.createRef<HTMLDivElement>(); + @observable _keyframeEditing = false; @observable _focusFilters: Opt<string[]>; // docFilters that are overridden when previewing a link to an anchor which has docFilters set on it @observable _focusRangeFilters: Opt<string[]>; // docRangeFilters that are overridden when previewing a link to an anchor which has docRangeFilters set on it @observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. @@ -147,6 +148,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return this.getTransformOverlay().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform); } + @action setKeyFrameEditing = (set: boolean) => this._keyframeEditing = set; + getKeyFrameEditing = () => this._keyframeEditing; onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.props.parentActive?.(outsideReaction) || this.backgroundActive || this.layoutDoc._viewType === CollectionViewType.Pile ? true : false; @@ -1051,7 +1054,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this />; } - addDocTab = action((doc: Doc, where: string) => { if (where === "inParent") { ((doc instanceof Doc) ? [doc] : doc).forEach(doc => { @@ -1076,7 +1078,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document._currentFrame); return { x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"), - transition: StrCast(layoutDoc.dataTransition), opacity: this.Document.editing ? 1 : Cast(opacity, "number", null), + transition: StrCast(layoutDoc.dataTransition), opacity: this._keyframeEditing ? 1 : Cast(opacity, "number", null), width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: "" }; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 358446a57..bbbc6572f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -83,6 +83,8 @@ export interface DocComponentView { reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling. shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected. + getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) + setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) } export interface DocumentViewSharedProps { renderDepth: number; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 682ec5356..3c1fda465 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -224,7 +224,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> setTimeout(() => targetDoc._viewTransition = undefined, 1010); this.nextKeyframe(targetDoc, activeItem); if (activeItem.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc); - else targetDoc.editing = true; + else targetDoc.keyFrameEditing = true; } _mediaTimer!: [NodeJS.Timeout, Doc]; @@ -1902,8 +1902,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div key="back" title="back frame" className="backKeyframe" onClick={e => { e.stopPropagation(); this.prevKeyframe(targetDoc, activeItem); }}> <FontAwesomeIcon icon={"caret-left"} size={"lg"} /> </div> - <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.editing ? "white" : "black", backgroundColor: targetDoc.editing ? PresColor.DarkBlue : PresColor.LightBlue }} - onClick={action(() => targetDoc.editing = !targetDoc.editing)} > + <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.keyFrameEditing ? "white" : "black", backgroundColor: targetDoc.keyFrameEditing ? PresColor.DarkBlue : PresColor.LightBlue }} + onClick={action(() => targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing)} > {NumCast(targetDoc._currentFrame)} </div> <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={e => { e.stopPropagation(); this.nextKeyframe(targetDoc, activeItem); }}> @@ -2063,7 +2063,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const targetDoc: Doc = this.targetDoc; const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); if (!activeItem.presProgressivize) { - targetDoc.editing = false; + targetDoc.keyFrameEditing = false; activeItem.presProgressivize = true; targetDoc.presProgressivize = true; targetDoc._currentFrame = 0; @@ -2074,7 +2074,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> activeItem.presProgressivize = false; targetDoc.presProgressivize = false; targetDoc._currentFrame = 0; - targetDoc.editing = true; + targetDoc.keyFrameEditing = true; } } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index f15a249da..ed412ad99 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -161,10 +161,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum }))); iframe.contentDocument.addEventListener('wheel', this.iframeWheel, false); //iframe.contentDocument.addEventListener('scroll', () => !this.active() && this._iframe && (this._iframe.scrollTop = NumCast(this.layoutDoc._scrollTop), false)); - iframe.contentDocument.addEventListener('scroll', () => { - console.log("Scroll = " + this._iframe?.scrollTop) - } - , true); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index e4c481014..d64db1ee1 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -571,7 +571,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp })); const uicontrols: ContextMenuProps[] = []; - uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Hide" : "Show"} Annotation Bar`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" }); + !Doc.UserDoc().noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" }); uicontrols.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" }); uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); @@ -748,8 +748,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this.props.contentsActive?.(this.active); this._cachedLinks = DocListCast(this.Document.links); - this._disposers.autoHeight = reaction(() => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), - ({ width, autoHeight, scrollHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight) + this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight()); + this._disposers.autoHeight = reaction(() => ({ scrollHeight: this.scrollHeight, width: NumCast(this.layoutDoc._width) }), + ({ width, scrollHeight }) => width && this.autoHeight && this.resetNativeHeight(scrollHeight) ); this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight }), diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 91bb321b2..68b1452f8 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -146,8 +146,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu (SelectionManager.Views().length === 1) && this.setupPdfJsViewer(); }, { fireImmediately: true }); - this._disposers.curPage = reaction( - () => this.Document._curPage, + this._disposers.curPage = reaction(() => this.Document._curPage, (page) => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page), { fireImmediately: true } ); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 2f9dc7f51..1c9ab0e7e 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -508,11 +508,11 @@ export namespace Doc { cloneMap.set(doc[Id], copy); if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy); const filter = [...exclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])]; - await Promise.all(Object.keys(doc).map(async key => { + await Promise.all([...Object.keys(doc), "links"].map(async key => { if (filter.includes(key)) return; const assignKey = (val: any) => !dontCreate && (copy[key] = val); const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - const field = ProxyField.WithoutProxy(() => doc[key]); + const field = key === "links" && Doc.IsPrototype(doc) ? doc[key] : ProxyField.WithoutProxy(() => doc[key]); const copyObjectField = async (field: ObjectField) => { const list = Cast(doc[key], listSpec(Doc)); const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts index 22ae454f8..88de3a19f 100644 --- a/src/fields/SchemaHeaderField.ts +++ b/src/fields/SchemaHeaderField.ts @@ -37,6 +37,7 @@ export const PastelSchemaPalette = new Map<string, string>([ ["red3", "#ffbebc"], ["red4", "#ffcbc1"], ["orange1", "#ffd5b3"], + ["gray", "#f1efeb"] ]); export const RandomPastel = () => Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * PastelSchemaPalette.size)]; diff --git a/src/fields/util.ts b/src/fields/util.ts index 5d98971da..6038a0534 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -302,20 +302,15 @@ export function setter(target: any, in_prop: string | symbol | number, value: an } export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { - const prop = in_prop; + let prop = in_prop; if (in_prop === AclSym) return target[AclSym]; if (in_prop === "toString" || (in_prop !== HeightSym && in_prop !== WidthSym && in_prop !== LayoutSym && typeof prop === "symbol")) return target.__fields[prop] || target[prop]; if (GetEffectiveAcl(target) === AclPrivate) return prop === HeightSym || prop === WidthSym ? returnZero : undefined; if (prop === LayoutSym) return target.__LAYOUT__; - let search = false; if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && prop.startsWith("_")) { - // if (!prop.startsWith("_")) { - // console.log(prop + " is deprecated - switch to _" + prop); - // prop = "_" + prop; - // } - if (!prop.startsWith("__")) search = true; - if (target.__LAYOUT__) return target.__LAYOUT__[prop] ?? (search ? target.__LAYOUT__[prop.substring(1)] : undefined); + if (!prop.startsWith("__")) prop = prop.substring(1); + if (target.__LAYOUT__) return target.__LAYOUT__[prop]; } if (prop === "then") {//If we're being awaited return undefined; @@ -326,7 +321,7 @@ export function getter(target: any, in_prop: string | symbol | number, receiver: if (SerializationHelper.IsSerializing()) { return target[prop]; } - return (search ? getFieldImpl(target, (prop as any as string).substring(1), receiver) : undefined) ?? getFieldImpl(target, prop, receiver); + return getFieldImpl(target, prop, receiver); } function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreProto: boolean = false): any { |