diff options
Diffstat (limited to 'src/client/views/collections')
12 files changed, 201 insertions, 110 deletions
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 5454c6f9f..4e4bd43bf 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -14,7 +14,8 @@ import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FieldView'; import './CollectionCarousel3DView.scss'; import { CollectionSubView } from './CollectionSubView'; -const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore +const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); + @observer export class CollectionCarousel3DView extends CollectionSubView() { @computed get scrollSpeed() { diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss index 4c2dcf9ab..95fda7b0a 100644 --- a/src/client/views/collections/CollectionNoteTakingView.scss +++ b/src/client/views/collections/CollectionNoteTakingView.scss @@ -1,7 +1,7 @@ @import '../global/globalCssVariables.module.scss'; .collectionNoteTakingView-DocumentButtons { - display: none; + opacity: 0; justify-content: space-between; margin: auto; } @@ -15,7 +15,6 @@ .editableView-container-editing-oneLine, .editableView-container-editing { - color: grey; padding: 10px; width: 100%; } @@ -29,7 +28,6 @@ .editableView-input { outline-color: black; letter-spacing: 2px; - color: grey; border: 0px; padding: 12px 10px 11px 10px; } @@ -41,7 +39,6 @@ .editableView-input { outline-color: black; letter-spacing: 2px; - color: grey; border: 0px; padding: 12px 10px 11px 10px; } @@ -51,9 +48,9 @@ display: flex; } -.collectionNoteTakingViewFieldColumn:hover { +.collectionNoteTakingViewFieldColumnHover:hover { .collectionNoteTakingView-DocumentButtons { - display: flex; + opacity: 1; } } @@ -100,6 +97,9 @@ flex-direction: column; height: max-content; } + .collectionNoteTakingViewFieldColumnHover { + min-height: 100%; // if we use this, then we can't have autoHeight + } .collectionSchemaView-previewDoc { height: 100%; @@ -250,7 +250,6 @@ overflow: visible; width: 100%; display: flex; - color: gray; align-items: center; } } @@ -262,10 +261,6 @@ background: $medium-gray; // overflow: hidden; overflow is visible so the color menu isn't hidden -ftong - .editableView-input { - color: $dark-gray; - } - .editableView-input:hover, .editableView-container-editing:hover, .editableView-container-editing-oneLine:hover { @@ -288,7 +283,6 @@ .editableView-container-editing-oneLine, .editableView-container-editing { - color: grey; padding: 10px; } @@ -301,7 +295,6 @@ .editableView-input { padding: 12px 10px 11px 10px; border: 0px; - color: grey; text-align: center; letter-spacing: 2px; outline-color: black; @@ -409,7 +402,6 @@ .editableView-container-editing-oneLine, .editableView-container-editing { - color: grey; padding: 10px; width: 100%; } @@ -423,13 +415,16 @@ .editableView-input { outline-color: black; letter-spacing: 2px; - color: grey; border: 0px; padding: 12px 10px 11px 10px; + &::placeholder { + color: black; + } } } .collectionNoteTakingView-addDocumentButton { + opacity: 0.5; font-size: 75%; letter-spacing: 2px; cursor: pointer; @@ -437,10 +432,12 @@ .editableView-input { outline-color: black; letter-spacing: 2px; - color: grey; border: 0px; padding: 12px 10px 11px 10px; } + &:hover { + opacity: unset; + } } .collectionNoteTakingView-addGroupButton { @@ -497,6 +494,24 @@ .rc-switch-checked .rc-switch-inner { left: 8px; } + + .collectionNoteTaking-pivotField { + display: none; + } + &:hover { + .collectionNoteTaking-pivotField { + display: unset; + } + } +} + +.collectionNoteTakingViewLight { + .collectionNoteTakingView-addDocumentButton, + .collectionNoteTakingView-addGroupButton { + .editableView-input::placeholder { + color: white; + } + } } @media only screen and (max-device-width: 480px) { diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 5b91216cb..d8a0aebb1 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -9,14 +9,15 @@ import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DivHeight, emptyFunction, returnZero, smoothScroll, Utils } from '../../../Utils'; +import { DivHeight, emptyFunction, lightOrDark, returnZero, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; -import { undoBatch } from '../../util/UndoManager'; +import { undoable, undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; +import { Colors } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView'; @@ -26,6 +27,7 @@ import './CollectionNoteTakingView.scss'; import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn'; import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider'; import { CollectionSubView } from './CollectionSubView'; +import { FieldsDropdown } from '../FieldsDropdown'; const _global = (window /* browser */ || global) /* node */ as any; /** @@ -40,7 +42,9 @@ export class CollectionNoteTakingView extends CollectionSubView() { _disposers: { [key: string]: IReactionDisposer } = {}; _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef<HTMLDivElement>(); - notetakingCategoryField = 'NotetakingCategory'; + @computed get notetakingCategoryField() { + return StrCast(this.dataDoc.notetaking_column, StrCast(this.layoutDoc.pivotField, 'notetaking_column')); + } public DividerWidth = 16; @observable docsDraggedRowCol: number[] = []; @observable _scroll = 0; @@ -152,25 +156,34 @@ export class CollectionNoteTakingView extends CollectionSubView() { ); }; + @computed get allFieldValues() { + return new Set(this.childDocs.map(doc => StrCast(doc[this.notetakingCategoryField]))); + } + componentDidMount() { super.componentDidMount?.(); document.addEventListener('pointerup', this.removeDocDragHighlight, true); - this._disposers.layout_autoHeight = reaction( - () => this.layoutDoc._layout_autoHeight, - layout_autoHeight => layout_autoHeight && this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight))) + + this._disposers.autoColumns = reaction( + () => (this.layoutDoc._notetaking_columns_autoCreate ? Array.from(this.allFieldValues) : undefined), + columns => undoable(() => columns?.filter(col => !this.colHeaderData.some(h => h.heading === col)).forEach(col => this.addColumn(col)), 'adding columns')(), + { fireImmediately: true } ); this._disposers.refList = reaction( () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }), ({ refList, autoHeight }) => { - if (autoHeight) refList.forEach(r => this.observer.observe(r)); - else this.observer.disconnect(); + if (autoHeight) { + refList.forEach(r => this.observer.observe(r)); + this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight))); + } else this.observer.disconnect(); }, { fireImmediately: true } ); } componentWillUnmount() { + this.observer.disconnect(); document.removeEventListener('pointerup', this.removeDocDragHighlight, true); super.componentWillUnmount(); Object.keys(this._disposers).forEach(key => this._disposers[key]()); @@ -297,7 +310,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { getDocWidth(d: Doc) { const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field); const existingHeader = this.colHeaderData.find(sh => sh.heading === heading); - const existingWidth = existingHeader?.width ? existingHeader.width : 0; + const existingWidth = this.layoutDoc._notetaking_columns_autoSize ? 1 / (this.colHeaderData.length ?? 1) : existingHeader?.width ? existingHeader.width : 0; const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth; const width = d.layout_fitWidth ? maxWidth : NumCast(d._width); return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth); @@ -369,7 +382,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { // we alter the pivot fields of the docs in case they are moved to a new column. const colIndex = this.getColumnFromXCoord(xCoord); const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading); - DragManager.docsBeingDragged.forEach(d => (d[this.notetakingCategoryField] = colHeader)); + DragManager.docsBeingDragged.map(doc => doc[DocData]).forEach(d => (d[this.notetakingCategoryField] = colHeader)); // used to notify sections to re-render this.docsDraggedRowCol.length = 0; const columnFromCoord = this.getColumnFromXCoord(xCoord); @@ -499,11 +512,12 @@ export class CollectionNoteTakingView extends CollectionSubView() { editableViewProps = () => ({ GetValue: () => '', - SetValue: this.addGroup, - contents: '+ New Column', + SetValue: this.addColumn, + contents: '+ Column', }); refList = () => this._refList; + backgroundColor = () => this._props.DocumentView?.().backgroundColor(); // sectionNoteTaking returns a CollectionNoteTakingViewColumn (which is an individual column) sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => ( @@ -511,7 +525,9 @@ export class CollectionNoteTakingView extends CollectionSubView() { key={heading?.heading ?? 'unset'} PanelWidth={this._props.PanelWidth} refList={this._refList} + backgroundColor={this.backgroundColor} select={this._props.select} + isContentActive={this.isContentActive} addDocument={this.addDocument} chromeHidden={this.chromeHidden} colHeaderData={this.colHeaderData} @@ -537,18 +553,26 @@ export class CollectionNoteTakingView extends CollectionSubView() { /> ); + @undoBatch + remColumn = (value: SchemaHeaderField) => { + const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null)); + if (value) { + const index = colHdrData.indexOf(value); + index !== -1 && colHdrData.splice(index, 1); + this.resizeColumns(colHdrData); + } + }; + // addGroup is called when adding a new columnHeader, adding a SchemaHeaderField to our list of // columnHeaders and resizing the existing columns to make room for our new one. @undoBatch - addGroup = (value: string) => { - if (this.colHeaderData) { - for (const header of this.colHeaderData) { - if (header.heading === value) { - alert('You cannot use an existing column name. Please try a new column name'); - return value; - } + addColumn = (value: string) => { + this.colHeaderData.forEach(header => { + if (header.heading === value) { + alert('You cannot use an existing column name. Please try a new column name'); + return value; } - } + }); const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null)); const newColWidth = 1 / (this.numGroupColumns + 1); columnHeaders.push(new SchemaHeaderField(value, undefined, undefined, newColWidth)); @@ -556,11 +580,25 @@ export class CollectionNoteTakingView extends CollectionSubView() { return true; }; + removeEmptyColumns = undoable(() => { + this.colHeaderData.filter(h => !this.allFieldValues.has(h.heading)).forEach(this.remColumn); + }, 'remove empty Columns'); + onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout if (!e.isPropagationStopped()) { const subItems: ContextMenuProps[] = []; - subItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' }); + subItems.push({ + description: `${this.layoutDoc._notetaking_columns_autoCreate ? 'Manually' : 'Automatically'} Create columns`, + event: () => (this.layoutDoc._notetaking_columns_autoCreate = !this.layoutDoc._notetaking_columns_autoCreate), + icon: 'computer', + }); + subItems.push({ description: 'Remove Empty Columns', event: this.removeEmptyColumns, icon: 'computer' }); + subItems.push({ + description: `${this.layoutDoc._notetaking_columns_autoSize ? 'Variable Size' : 'Autosize'} Columns`, + event: () => (this.layoutDoc._notetaking_columns_autoSize = !this.layoutDoc._notetaking_columns_autoSize), + icon: 'plus', + }); subItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: 'plus' }); subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' }); ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' }); @@ -613,12 +651,11 @@ export class CollectionNoteTakingView extends CollectionSubView() { TraceMobx(); return ( <div - className="collectionNoteTakingView" + className={`collectionNoteTakingView ${lightOrDark(this.backgroundColor()) === Colors.WHITE ? 'collectionNoteTakingViewLight' : ''}`} ref={this.createRef} - key="notes" style={{ - overflowY: this._props.isContentActive() ? 'auto' : 'hidden', - background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor), + overflowY: this.isContentActive() ? 'auto' : 'hidden', + background: this.backgroundColor(), pointerEvents: this.backgroundEvents, }} onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))} @@ -629,6 +666,16 @@ export class CollectionNoteTakingView extends CollectionSubView() { onContextMenu={this.onContextMenu} onWheel={e => this._props.isContentActive() && e.stopPropagation()}> <>{this.renderedSections}</> + <div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}> + <FieldsDropdown + Document={this.Document} + selectFunc={undoable(fieldKey => { + this.layoutDoc._pivotField = fieldKey; + this.removeEmptyColumns(); + }, 'change pivot field')} + placeholder={StrCast(this.layoutDoc._pivotField)} + /> + </div> </div> ); } diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index db178d500..448b11b05 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -1,10 +1,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable } from 'mobx'; +import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnEmptyString } from '../../../Utils'; +import { lightOrDark, returnEmptyString } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { Id } from '../../../fields/FieldSymbols'; import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; @@ -26,6 +25,7 @@ import './CollectionNoteTakingView.scss'; interface CSVFieldColumnProps { Document: Doc; TemplateDataDocument: Opt<Doc>; + backgroundColor?: (() => string) | undefined; docList: Doc[]; heading: string; pivotField: string; @@ -38,6 +38,7 @@ interface CSVFieldColumnProps { gridGap: number; headings: () => object[]; select: (ctrlPressed: boolean) => void; + isContentActive: () => boolean | undefined; renderChildren: (docs: Doc[]) => JSX.Element[]; addDocument: (doc: Doc | Doc[]) => boolean; createDropTarget: (ele: HTMLDivElement) => void; @@ -57,10 +58,16 @@ interface CSVFieldColumnProps { */ @observer export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSVFieldColumnProps> { - @observable private _background = 'inherit'; + @observable private _hover = false; + + constructor(props: CSVFieldColumnProps) { + super(props); + makeObservable(this); + } // columnWidth returns the width of a column in absolute pixels @computed get columnWidth() { + if (this._props.Document._notetaking_columns_autoSize) return this._props.availableWidth / (this._props.colHeaderData?.length || 1); if (!this._props.colHeaderData || !this._props.headingObject || this._props.colHeaderData.length === 1) return `${(this._props.availableWidth / this._props.PanelWidth()) * 100}%`; const i = this._props.colHeaderData.findIndex(hd => hd.heading === this._props.headingObject?.heading && hd.color === this._props.headingObject.color); return ((this._props.colHeaderData[i].width * this._props.availableWidth) / this._props.PanelWidth()) * 100 + '%'; @@ -122,8 +129,8 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV return false; }; - @action pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4'); - @action pointerLeave = () => (this._background = 'inherit'); + @action pointerEntered = () => (this._hover = true); + @action pointerLeave = () => (this._hover = false); @undoBatch addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true); @@ -147,7 +154,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV deleteColumn = () => { const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null)); if (this._props.headingObject) { - this._props.docList.forEach(d => (d[this._props.pivotField] = undefined)); + // this._props.docList.forEach(d => (d[DocData][this._props.pivotField] = undefined)); colHdrData.splice(colHdrData.indexOf(this._props.headingObject), 1); this._props.resizeColumns(colHdrData); } @@ -273,11 +280,11 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV </div> {!this._props.chromeHidden ? ( - <div className="collectionNoteTakingView-DocumentButtons" style={{ marginBottom: 10 }}> - <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton"> - <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} /> + <div className="collectionNoteTakingView-DocumentButtons" style={{ display: this._props.isContentActive() ? 'flex' : 'none', marginBottom: 10 }}> + <div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}> + <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents={'+ Node'} menuCallback={this.menuCallback} /> </div> - <div key={`${this._props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton"> + <div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}> <EditableView {...this._props.editableViewProps()} /> </div> </div> @@ -292,17 +299,17 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV TraceMobx(); return ( <div - className="collectionNoteTakingViewFieldColumn" - key={this._heading} + className="collectionNoteTakingViewFieldColumnHover" + onPointerEnter={this.pointerEntered} + onPointerLeave={this.pointerLeave} style={{ width: this.columnWidth, - background: this._background, + background: this._hover && SnappingManager.IsDragging ? '#b4b4b4' : 'inherit', marginLeft: this._props.headings().findIndex((h: any) => h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Document.xMargin) : 0, - }} - ref={this.createColumnDropRef} - onPointerEnter={this.pointerEntered} - onPointerLeave={this.pointerLeave}> - {this.innards} + }}> + <div className="collectionNoteTakingViewFieldColumn" key={this._heading} ref={this.createColumnDropRef}> + {this.innards} + </div> </div> ); } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 3a62a53d7..bf0393883 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -316,7 +316,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection <DocumentView ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(doc, r))} Document={doc} - TemplateDataDocument={dataDoc ?? (Doc.AreProtosEqual(doc[DocData], doc) ? undefined : doc[DocData])} + TemplateDataDocument={dataDoc} renderDepth={this._props.renderDepth + 1} PanelWidth={panelWidth} PanelHeight={panelHeight} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 641e01b81..c5292f880 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -12,7 +12,7 @@ import { TraceMobx } from '../../../fields/util'; import { DivHeight, DivWidth, emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; -import { DragManager } from '../../util/DragManager'; +import { DragManager, dropActionType } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; @@ -66,11 +66,26 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< _ele: HTMLElement | null = null; + protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => { + const dragData = de.complete.docDragData; + if (dragData) { + const sourceDragAction = dragData.dropAction; + const sameCollection = !dragData.draggedDocuments.some(d => !this._props.docList.includes(d)); + dragData.dropAction = !sameCollection // if doc from another tree + ? sourceDragAction || targetDropAction // then use the source's dragAction otherwise the target's + : sourceDragAction === dropActionType.inPlace // if source drag is inPlace + ? sourceDragAction // keep the doc in place + : dropActionType.same; // otherwise use same tree semantics to move within tree + + e.stopPropagation(); + } + }; + // This is likely similar to what we will be doing. Why do we need to make these refs? // is that the only way to have drop targets? createColumnDropRef = (ele: HTMLDivElement | null) => { this.dropDisposer?.(); - if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document); + if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document, this.onInternalPreDrop.bind(this)); else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1); this._ele = ele; }; @@ -345,7 +360,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< <div> <div key={`${heading}-stack`} - className={`collectionStackingView-masonrySingle`} + className="collectionStackingView-masonrySingle" style={{ padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`, margin: 'auto', diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index ecc2aea31..32198e3a2 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -4,7 +4,7 @@ import * as rp from 'request-promise'; import { Utils, returnFalse } from '../../../Utils'; import CursorField from '../../../fields/CursorField'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; -import { AclPrivate } from '../../../fields/DocSymbols'; +import { AclPrivate, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; @@ -196,13 +196,16 @@ export function CollectionSubView<X>(moreProps?: X) { @undoBatch protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {} - protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, dropAction: dropActionType) { - if (de.complete.docDragData) { - // if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding. - // so we check if our collection has a dropAction set on it and if so, we use that instead. - if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d))) { - de.complete.docDragData.dropAction = dropAction; - } + protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) { + const dragData = de.complete.docDragData; + if (dragData) { + const sourceDragAction = dragData.dropAction; + const sameCollection = !dragData.draggedDocuments.some(d => d.embedContainer !== this._props.Document); + dragData.dropAction = !sameCollection // if doc from another tree + ? sourceDragAction || targetDropAction // then use the source's dragAction otherwise the target's + : sourceDragAction === dropActionType.inPlace // if source drag is inPlace + ? sourceDragAction // keep the doc in place + : dropActionType.same; // otherwise use same tree semantics to move within tree e.stopPropagation(); } } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 293c79119..5741fc29b 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -157,14 +157,12 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree const dragData = de.complete.docDragData; if (dragData) { const sourceDragAction = dragData.dropAction; - const sameTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.Document); - const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d)); - dragData.dropAction = - targetDropAction && !isAlreadyInTree() // if dropped document is not in the tree - ? targetDropAction // then use the target's drop action if it's specified - : !sameTree() || sourceDragAction === dropActionType.inPlace // if doc from another tree, or a non inPlace source drag action is specified - ? sourceDragAction // use the source dragAction - : dropActionType.same; // otherwise use same tree semantics to move within tree + const sameTree = dragData.treeViewDoc?.[DocData] === this.dataDoc; + dragData.dropAction = !sameTree // if doc from another tree + ? sourceDragAction || targetDropAction // then use the source's dragAction otherwise the target's + : sourceDragAction === dropActionType.inPlace // if source drag is inPlace + ? sourceDragAction // keep the doc in place + : dropActionType.same; // otherwise use same tree semantics to move within tree e.stopPropagation(); } }; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 5eac6cb09..4fd49f8fe 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -35,7 +35,7 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; import { CollectionView } from './CollectionView'; import { TreeSort } from './TreeSort'; import './TreeView.scss'; -const { default: { TREE_BULLET_WIDTH } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore +const { TREE_BULLET_WIDTH } = require('../global/globalCssVariables.module.scss'); // prettier-ignore export interface TreeViewProps { treeView: CollectionTreeView; @@ -449,7 +449,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { const addDoc = inside ? this.localAdd : parentAddDoc; const canAdd = !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add') || forceAdd; if (canAdd && (dropAction !== dropActionType.inPlace || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) { - const move = (!dropAction || canEmbed || dropAction === dropActionType.proto || dropAction === dropActionType.move || dropAction === dropActionType.same || dropAction === dropActionType.inPlace) && moveDocument; + const move = + (!dropAction || (canEmbed && dropAction !== dropActionType.copy) || dropAction === dropActionType.proto || dropAction === dropActionType.move || dropAction === dropActionType.same || dropAction === dropActionType.inPlace) && + moveDocument; this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = true); const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === dropActionType.proto ? addDoc(d) : false) : addDoc(d)) || added, false); this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = false); @@ -928,7 +930,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { case 'Tab': e.stopPropagation?.(); e.preventDefault?.(); - setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150); + setTimeout(() => RichTextMenu.Instance?.TextView?.EditorView?.focus(), 150); UndoManager.RunInBatch(() => (e.shiftKey ? this._props.outdentDocument?.(true) : this._props.indentDocument?.(true)), 'tab'); return true; case 'Backspace': diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx index 08dfb32ad..0acc99360 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx @@ -14,8 +14,8 @@ export interface CollectionFreeFormViewBackgroundGridProps { nativeDimScaling: () => number; zoomScaling: () => number; layoutDoc: Doc; - cachedCenteringShiftX: number; - cachedCenteringShiftY: number; + centeringShiftX: number; + centeringShiftY: number; } @observer export class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> { @@ -32,7 +32,7 @@ export class CollectionFreeFormBackgroundGrid extends React.Component<Collection const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace; const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace; const strokeStyle = this.props.color(); - return !this.props.nativeDimScaling() ? null : ( + return ( <canvas className="collectionFreeFormView-grid" width={w} @@ -41,8 +41,8 @@ export class CollectionFreeFormBackgroundGrid extends React.Component<Collection ref={el => { const ctx = el?.getContext('2d'); if (ctx) { - const Cx = this.props.cachedCenteringShiftX % renderGridSpace; - const Cy = this.props.cachedCenteringShiftY % renderGridSpace; + const Cx = this.props.centeringShiftX % renderGridSpace; + const Cy = this.props.centeringShiftY % renderGridSpace; ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling())); ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]); ctx.clearRect(0, 0, w, h); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 791124f50..079a5d977 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -169,17 +169,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @computed get nativeHeight() { return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } - @computed get cachedCenteringShiftX(): number { - const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling; + @computed get centeringShiftX(): number { + const scaling = this.nativeDimScaling; return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : this._props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } - @computed get cachedCenteringShiftY(): number { + @computed get centeringShiftY(): number { + const panLocAtCenter = !(this._props.isAnnotationOverlay || this._props.originTopLeft); + if (!panLocAtCenter) return 0; const dv = this.DocumentView?.(); - const fitWidth = this._props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth; - const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling; + const aspect = !(this._props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth) && dv?.nativeWidth && dv?.nativeHeight; + const scaling = this.nativeDimScaling; // if freeform has a native aspect, then the panel height needs to be adjusted to match it - const aspect = dv?.nativeWidth && dv?.nativeHeight && !fitWidth ? dv.nativeHeight / dv.nativeWidth : this._props.PanelHeight() / this._props.PanelWidth(); - return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : (aspect * this._props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections + const height = aspect ? (dv.nativeHeight / dv.nativeWidth) * this._props.PanelWidth() : this._props.PanelHeight(); + return height / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } @computed get panZoomXf() { return new Transform(this.panX(), this.panY(), 1 / this.zoomScaling()); @@ -187,7 +189,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @computed get screenToFreeformContentsXf() { return this._props .ScreenToLocalTransform() // - .translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY) + .translate(-this.centeringShiftX, -this.centeringShiftY) .transform(this.panZoomXf); } @@ -250,8 +252,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1)); panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1)); zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); //, NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1)); - PanZoomCenterXf = () => - this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; + PanZoomCenterXf = () => (this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.centeringShiftX}px, ${this.centeringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`); ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy(); getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); isAnyChildContentActive = () => this._props.isAnyChildContentActive(); @@ -1114,9 +1115,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (scale !== undefined) { const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context const newScale = - scale === 0 - ? NumCast(this.layoutDoc[this.scaleFieldKey]) - : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this._props.PanelWidth() / Math.abs(bounds.width), this._props.PanelHeight() / Math.abs(bounds.height))); + scale === 0 ? NumCast(this.layoutDoc[this.scaleFieldKey]) : Math.min(maxZoom, (1 / this.nativeDimScaling) * scale * Math.min(this._props.PanelWidth() / Math.abs(bounds.width), this._props.PanelHeight() / Math.abs(bounds.height))); return { panX: this._props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2, panY: this._props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2, @@ -1413,7 +1412,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection presentation_transition: 500, annotationOn: this.Document, }); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, this.Document); + PresBox.pinDocView( + anchor, + { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ? { ...pinProps.pinData, poslayoutview: pinProps.pinData.dataview } : {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, + this.Document + ); if (addAsAnnotation) { if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) { @@ -1763,8 +1766,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection zoomScaling={this.zoomScaling} layoutDoc={this.layoutDoc} isAnnotationOverlay={this.isAnnotationOverlay} - cachedCenteringShiftX={this.cachedCenteringShiftX} - cachedCenteringShiftY={this.cachedCenteringShiftY} + centeringShiftX={this.centeringShiftX} + centeringShiftY={this.centeringShiftY} /> </div> ); @@ -1812,7 +1815,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } @computed get nativeDimScaling() { - if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 0; + if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 1; const nw = this.nativeWidth; const nh = this.nativeHeight; const hscale = nh ? this._props.PanelHeight() / nh : 1; @@ -1866,9 +1869,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection style={{ pointerEvents: this._props.isContentActive() && SnappingManager.IsDragging ? 'all' : (this._props.pointerEvents?.() as any), textAlign: this.isAnnotationOverlay ? 'initial' : undefined, - transform: `scale(${this.nativeDimScaling || 1})`, - width: `${100 / (this.nativeDimScaling || 1)}%`, - height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`, + transform: `scale(${this.nativeDimScaling})`, + width: `${100 / this.nativeDimScaling}%`, + height: this._props.getScrollHeight?.() ?? `${100 / this.nativeDimScaling}%`, }}> {this.paintFunc ? ( <FormattedTextBox {...this.props} /> // need this so that any live dashfieldviews will update the underlying text that the code eval reads diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index df023b00f..6a956f2ac 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -28,7 +28,7 @@ import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; -const { default: { SCHEMA_NEW_NODE_HEIGHT } } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore +const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore export enum ColumnType { Number, |
