diff options
| author | Sophie Zhang <sophie_zhang@brown.edu> | 2023-07-18 15:06:22 -0400 |
|---|---|---|
| committer | Sophie Zhang <sophie_zhang@brown.edu> | 2023-07-18 15:06:22 -0400 |
| commit | 40784b7265851b27e043c07e5f9038a0b29af8b7 (patch) | |
| tree | 1f9e154913820cc1cb5952a9d444d9a0eca86c29 /src/client/views/collections | |
| parent | 162ca319eae256be523f2ee75e7aae7a9a408e37 (diff) | |
| parent | 267f5d7c6a87b955c2fa2121c6db7e01cfc1c148 (diff) | |
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views/collections')
35 files changed, 555 insertions, 1119 deletions
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 0eb61a0b2..ea02bcd4c 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -4,7 +4,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, Opt } from '../../../fields/Doc'; import { NumCast, ScriptCast, StrCast } from '../../../fields/Types'; -import { returnFalse, returnZero, StopEvent } from '../../../Utils'; +import { emptyFunction, returnFalse, returnZero, StopEvent } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; @@ -60,6 +60,8 @@ export class CollectionCarouselView extends CollectionSubView() { NativeHeight={returnZero} onDoubleClick={this.onContentDoubleClick} onClick={this.onContentClick} + isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.props.isContentActive} + isContentActive={this.props.childContentsActive ?? this.props.isContentActive() === false ? returnFalse : emptyFunction} hideCaptions={show_captions ? true : false} renderDepth={this.props.renderDepth + 1} LayoutTemplate={this.props.childLayoutTemplate} diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 78e44dfa2..4c15d5eed 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -15,6 +15,12 @@ cursor: grab; color: $black; } +.collectiondockingview-container .lm_splitter { + opacity: 0.2; + &:hover { + opacity: 1; + } +} .lm_title.focus-visible { -webkit-appearance: none; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index e9cc2c894..8d1b46ebb 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -8,7 +8,7 @@ import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { inheritParentAcls } from '../../../fields/util'; +import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util'; import { emptyFunction, incrementTitleCopy } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; @@ -28,6 +28,8 @@ import './CollectionDockingView.scss'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { TabDocView } from './TabDocView'; +import { DocumentManager } from '../../util/DocumentManager'; +import { AclAdmin, AclEdit } from '../../../fields/DocSymbols'; import React = require('react'); const _global = (window /* browser */ || global) /* node */ as any; @@ -174,9 +176,7 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) { - return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1 - ? CollectionDockingView.CloseSplit(doc) - : CollectionDockingView.AddSplit(doc, location, stack, panelName, keyValue); + return Array.from(CollectionDockingView.Instance?.tabMap.keys() ?? []).findIndex(tab => tab.DashDoc === doc) !== -1 ? CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName, keyValue); } // @@ -184,7 +184,7 @@ export class CollectionDockingView extends CollectionSubView() { // @action public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) { - if (document?._type_collection === CollectionViewType.Docking) return DashboardView.openDashboard(document); + if (document?._type_collection === CollectionViewType.Docking && !keyValue) return DashboardView.openDashboard(document); if (!CollectionDockingView.Instance) return false; const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document && !keyValue); if (tab) { @@ -361,6 +361,7 @@ export class CollectionDockingView extends CollectionSubView() { } catch (e) {} this._goldenLayout?.destroy(); window.removeEventListener('resize', this.onResize); + window.removeEventListener('mouseup', this.onPointerUp); this._reactionDisposer?.(); this._lightboxReactionDisposer?.(); @@ -385,8 +386,13 @@ export class CollectionDockingView extends CollectionSubView() { .map(f => f as Doc); const changesMade = this.props.Document.dockingConfig !== json; if (changesMade) { - this.props.Document.dockingConfig = json; - this.props.Document.data = new List<Doc>(docs); + if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) { + this.layoutDoc.dockingConfig = json; + this.layoutDoc.data = new List<Doc>(docs); + } else { + Doc.SetInPlace(this.rootDoc, 'dockingConfig', json, true); + Doc.SetInPlace(this.rootDoc, 'data', new List<Doc>(docs), true); + } } this._flush?.end(); this._flush = undefined; @@ -411,7 +417,14 @@ export class CollectionDockingView extends CollectionSubView() { if (!htmlTarget.closest('*.lm_content') && (htmlTarget.closest('*.lm_tab') || htmlTarget.closest('*.lm_stack'))) { const className = typeof htmlTarget.className === 'string' ? htmlTarget.className : ''; if (className.includes('lm_maximise')) this._flush = UndoManager.StartBatch('tab maximize'); - else if (!className.includes('lm_close')) DocServer.UPDATE_SERVER_CACHE(); + else { + const tabTarget = (e.target as HTMLElement)?.parentElement?.className.includes('lm_tab') ? (e.target as HTMLElement).parentElement : (e.target as HTMLElement); + const map = Array.from(this.tabMap).find(tab => tab.element[0] === tabTarget); + if (map?.DashDoc && DocumentManager.Instance.getFirstDocumentView(map.DashDoc)) { + SelectionManager.SelectView(DocumentManager.Instance.getFirstDocumentView(map.DashDoc), false); + } + if (!className.includes('lm_close')) DocServer.UPDATE_SERVER_CACHE(); + } } } if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { @@ -453,7 +466,7 @@ export class CollectionDockingView extends CollectionSubView() { const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeEmbedding(origtabdoc)); if (newtabdocs.length) { Doc.GetProto(newtab).data = new List<Doc>(newtabdocs); - newtabdocs.forEach(ntab => (ntab.embedContainer = newtab)); + newtabdocs.forEach(ntab => Doc.SetContainer(ntab, newtab)); } json = json.replace(origtab[Id], newtab[Id]); return newtab; @@ -473,7 +486,7 @@ export class CollectionDockingView extends CollectionSubView() { tabDestroyed = (tab: any) => { this._flush = this._flush ?? UndoManager.StartBatch('tab movement'); - if (tab.DashDoc && ![DocumentType.KVP, DocumentType.PRES].includes(tab.DashDoc?.type)) { + if (tab.DashDoc && ![DocumentType.PRES].includes(tab.DashDoc?.type) && !tab.contentItem.config.props.keyValue) { Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc); Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); } @@ -504,7 +517,7 @@ export class CollectionDockingView extends CollectionSubView() { _layout_fitWidth: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); - this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); + inheritParentAcls(this.rootDoc, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }); @@ -547,7 +560,7 @@ export class CollectionDockingView extends CollectionSubView() { _freeform_backgroundGrid: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); - this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); + inheritParentAcls(this.dataDoc, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }) diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 64f9c6a87..6f88f6727 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -98,14 +98,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr rowDrop = action((e: Event, de: DragManager.DropEvent) => { this._createEmbeddingSelected = false; if (de.complete.docDragData) { - this.props.parent.Document.dropConverter instanceof ScriptField && this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData }); 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)); - this.props.parent.onInternalDrop(e, de); - e.stopPropagation(); + if (this.props.parent.onInternalDrop(e, de)) { + de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc)); + } + return true; } + return false; }); getValue = (value: string): any => { diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss index c35f088a6..6eeccc94e 100644 --- a/src/client/views/collections/CollectionMenu.scss +++ b/src/client/views/collections/CollectionMenu.scss @@ -8,652 +8,15 @@ background-color: $dark-gray; height: 35px; border-bottom: $standard-border; - padding-right: 5px; + padding: 0 10px; align-items: center; + overflow-x: scroll; + &::-webkit-scrollbar { + display: none; + } - .collectionMenu-hardCodedButton { - cursor: pointer; - color: $white; - width: 25px; - height: 25px; - padding: 5; - text-align: center; + .hardCodedButtons { display: flex; - justify-content: center; - align-items: center; - position: relative; - transition: 0.2s; - border-radius: 3px; - - &:hover { - background-color: rgba(0, 0, 0, 0.2); - } + flex-direction: row; } -} - -// .collectionMenu-cont { -// position: relative; -// display: inline-flex; -// width: 100%; -// opacity: 0.9; -// z-index: 901; -// transition: top .5s; -// background: $dark-gray; -// color: $white; -// transform-origin: top left; -// top: 0; -// width: 100%; - -// .recordButtonOutline { -// border-radius: 100%; -// width: 18px; -// height: 18px; -// border: solid 1px $white; -// display: flex; -// align-items: center; -// justify-content: center; -// } - -// .recordButtonInner { -// border-radius: 100%; -// width: 70%; -// height: 70%; -// background: $white; -// } - -// .collectionMenu { -// display: flex; -// height: 100%; -// overflow: visible; -// z-index: 901; -// border: unset; - -// .collectionMenu-divider { -// height: 100%; -// margin-left: 3px; -// margin-right: 3px; -// width: 2px; -// background-color: $medium-gray; -// } - -// .collectionViewBaseChrome { -// display: flex; -// align-items: center; - -// .collectionViewBaseChrome-viewPicker { -// font-size: $small-text; -// outline-color: $black; -// color: $white; -// border: none; -// background: $dark-gray; -// } - -// .collectionViewBaseChrome-viewPicker:focus { -// outline: none; -// border: none; -// } - -// .collectionViewBaseChrome-viewPicker:active { -// outline-color: $black; -// } - -// .collectionViewBaseChrome-button { -// font-size: $small-text; -// text-transform: uppercase; -// letter-spacing: 2px; -// background: $white; -// color: $pink; -// outline-color: $black; -// border: none; -// padding: 12px 10px 11px 10px; -// margin-left: 10px; -// } - -// .collectionViewBaseChrome-cmdPicker { -// margin-left: 3px; -// margin-right: 0px; -// font-size: $small-text; -// text-transform: capitalize; -// color: $white; -// border: none; -// background: $dark-gray; -// } - -// .collectionViewBaseChrome-cmdPicker:focus { -// border: none; -// outline: none; -// } - -// .commandEntry-outerDiv { -// pointer-events: all; -// background-color: transparent; -// display: flex; -// flex-direction: row; -// align-items: center; -// justify-content: center; -// height: 100%; -// overflow: hidden; - -// .commandEntry-drop { -// color: $white; -// width: 30px; -// margin-top: auto; -// margin-bottom: auto; -// } -// } - -// .commandEntry-outerDiv:hover{ -// background-color: $drop-shadow; - -// .collectionViewBaseChrome-viewPicker, -// .collectionViewBaseChrome-cmdPicker{ -// background: $dark-gray; -// } -// } - -// .collectionViewBaseChrome-collapse { -// transition: all .5s, opacity 0.3s; -// position: absolute; -// width: 30px; -// transform-origin: top left; -// pointer-events: all; -// // margin-top: 10px; -// } - -// @media only screen and (max-device-width: 480px) { -// .collectionViewBaseChrome-collapse { -// display: none; -// } -// } - -// .collectionViewBaseChrome-template, -// .collectionViewBaseChrome-viewModes { -// align-items: center; -// height: 100%; -// display: flex; -// background: transparent; -// color: $medium-gray; -// justify-content: center; -// } - -// .collectionViewBaseChrome-viewSpecs { -// margin-left: 5px; -// display: grid; -// border: none; -// border-right: solid $medium-gray 1px; - -// .collectionViewBaseChrome-filterIcon { -// position: relative; -// display: flex; -// margin: auto; -// background: $dark-gray; -// color: $white; -// width: 30px; -// height: 30px; -// align-items: center; -// justify-content: center; -// border: none; -// border-right: solid $medium-gray 1px; -// } - -// .collectionViewBaseChrome-viewSpecsInput { -// padding: 12px 10px 11px 10px; -// border: 0px; -// color: $medium-gray; -// text-align: center; -// letter-spacing: 2px; -// outline-color: $black; -// font-size: $small-text; -// background: $white; -// height: 100%; -// width: 75px; -// } - -// .collectionViewBaseChrome-viewSpecsMenu { -// overflow: hidden; -// transition: height .5s, display .5s; -// position: absolute; -// top: 60px; -// z-index: 100; -// display: flex; -// flex-direction: column; -// background: $white; -// box-shadow: $medium-gray 2px 2px 4px; - -// .qs-datepicker { -// left: unset; -// right: 0; -// } - -// .collectionViewBaseChrome-viewSpecsMenu-row { -// display: grid; -// grid-template-columns: 150px 200px 150px; -// margin-top: 10px; -// margin-right: 10px; - -// .collectionViewBaseChrome-viewSpecsMenu-rowLeft, -// .collectionViewBaseChrome-viewSpecsMenu-rowMiddle, -// .collectionViewBaseChrome-viewSpecsMenu-rowRight { -// font-size: $small-text; -// letter-spacing: 2px; -// color: $medium-gray; -// margin-left: 10px; -// padding: 5px; -// border: none; -// outline-color: $black; -// } -// } - -// .collectionViewBaseChrome-viewSpecsMenu-lastRow { -// display: grid; -// grid-template-columns: 1fr 1fr 1fr; -// grid-gap: 10px; -// margin: 10px; -// } -// } -// } -// } - -// .collectionStackingViewChrome-cont, -// .collectionTreeViewChrome-cont, -// .collection3DCarouselViewChrome-cont { -// display: flex; -// justify-content: space-between; -// } - -// .collectionGridViewChrome-cont { -// display: flex; -// margin-left: 10; - -// .collectionGridViewChrome-viewPicker { -// font-size: $small-text; -// //text-transform: uppercase; -// //letter-spacing: 2px; -// background: $dark-gray; -// color: $white; -// outline-color: $black; -// color: $white; -// border: none; -// border-right: solid $medium-gray 1px; -// } - -// .collectionGridViewChrome-viewPicker:active { -// outline-color: $black; -// } - -// .grid-control { -// align-self: center; -// display: flex; -// flex-direction: row; -// margin-right: 5px; - -// .grid-icon { -// margin-right: 5px; -// align-self: center; -// } - -// .flexLabel { -// margin-bottom: 0; -// } - -// .collectionGridViewChrome-entryBox { -// width: 50%; -// color: $black; -// } - -// .collectionGridViewChrome-columnButton { -// color: $black; -// } -// } -// } - -// .collectionStackingViewChrome-sort, -// .collectionTreeViewChrome-sort { -// display: flex; -// align-items: center; -// justify-content: space-between; - -// .collectionStackingViewChrome-sortIcon, -// .collectionTreeViewChrome-sortIcon { -// transition: transform .5s; -// margin-left: 10px; -// } -// } - -// button:hover { -// transform: scale(1); -// } - - -// .collectionStackingViewChrome-pivotField-cont, -// .collectionTreeViewChrome-pivotField-cont, -// .collection3DCarouselViewChrome-scrollSpeed-cont { -// justify-self: right; -// align-items: center; -// display: flex; -// grid-auto-columns: auto; -// font-size: $small-text; -// letter-spacing: 2px; - -// .collectionStackingViewChrome-pivotField-label, -// .collectionTreeViewChrome-pivotField-label, -// .collection3DCarouselViewChrome-scrollSpeed-label { -// grid-column: 1; -// margin-right: 7px; -// user-select: none; -// font-family: $sans-serif; -// letter-spacing: normal; -// } - -// .collectionStackingViewChrome-sortIcon { -// transition: transform .5s; -// grid-column: 3; -// text-align: center; -// display: flex; -// justify-content: center; -// align-items: center; -// cursor: pointer; -// width: 25px; -// height: 25px; -// border-radius: 100%; -// } - -// .collectionStackingViewChrome-sortIcon:hover { -// background-color: $drop-shadow; -// } - -// .collectionStackingViewChrome-pivotField, -// .collectionTreeViewChrome-pivotField, -// .collection3DCarouselViewChrome-scrollSpeed { -// color: $white; -// grid-column: 2; -// grid-row: 1; -// width: 90%; -// min-width: 100px; -// display: flex; -// height: 80%; -// border-radius: 7px; -// align-items: center; -// background: $white; - -// .editable-view-input, -// input, -// .editableView-container-editing-oneLine, -// .editableView-container-editing { -// margin: auto; -// border: 0px; -// color: $light-gray !important; -// text-align: center; -// letter-spacing: 2px; -// outline-color: $black; -// height: 100%; -// } - -// .react-autosuggest__container { -// margin: 0; -// color: $medium-gray; -// padding: 0px; -// } -// } -// } - -// .collectionStackingViewChrome-pivotField:hover, -// .collectionTreeViewChrome-pivotField:hover, -// .collection3DCarouselViewChrome-scrollSpeed:hover { -// cursor: text; -// } - -// } -// } - -// .collectionMenu-webUrlButtons { -// margin-left: 44; -// background: lightGray; -// display: flex; -// } - -// .webBox-urlEditor { -// position: relative; -// opacity: 0.9; -// z-index: 901; -// transition: top .5s; - -// .urlEditor { -// display: grid; -// grid-template-columns: 1fr auto; -// padding-bottom: 10px; -// overflow: hidden; -// margin-top: 5px; -// height: 35px; - -// .editorBase { -// display: flex; - -// .editor-collapse { -// transition: all .5s, opacity 0.3s; -// position: absolute; -// width: 40px; -// transform-origin: top left; -// } - -// .switchToText { -// color: $medium-gray; -// } - -// .switchToText:hover { -// color: $dark-gray; -// } -// } - -// button:hover { -// transform: scale(1); -// } -// } -// } - -// .collectionMenu-urlInput { -// padding: 12px 10px 11px 10px; -// border: 0px; -// color: $black; -// font-size: $small-text; -// letter-spacing: 2px; -// outline-color: $black; -// background: $white; -// width: 100%; -// min-width: 350px; -// margin-right: 10px; -// height: 100%; -// } - -// .collectionFreeFormMenu-cont { -// display: inline-flex; -// position: relative; -// align-items: center; -// height: 100%; - -// .color-previewI { -// width: 60%; -// top: 80%; -// position: absolute; -// height: 4px; -// } - -// .color-previewII { -// width: 80%; -// height: 80%; -// margin-left: 10%; -// position: absolute; -// bottom: 5; -// } - -// .btn-group { -// display: grid; -// grid-template-columns: auto auto auto auto; -// margin: auto; -// /* Make the buttons appear below each other */ -// } - -// .btn-draw { -// display: inline-flex; -// margin: auto; -// /* Make the buttons appear below each other */ -// } - -// .fwdKeyframe, -// .numKeyframe, -// .backKeyframe { -// cursor: pointer; -// position: relative; -// width: 20; -// height: 30; -// bottom: 0; -// background: $dark-gray; -// display: inline-flex; -// align-items: center; -// color: $white; -// } - -// .backKeyframe { -// svg { -// display: block; -// margin: auto; -// } -// } - - -// .numKeyframe { -// flex-direction: column; -// padding-top: 5px; -// } - -// .fwdKeyframe { -// svg { -// display: block; -// margin: auto; -// } - -// border-right: solid $medium-gray 1px; -// } -// } - -// .collectionSchemaViewChrome-cont { -// display: flex; -// font-size: $small-text; - -// .collectionSchemaViewChrome-toggle { -// display: flex; -// margin-left: 10px; -// } - -// .collectionSchemaViewChrome-label { -// text-transform: uppercase; -// letter-spacing: 2px; -// margin-right: 5px; -// display: flex; -// flex-direction: column; -// justify-content: center; -// } - -// .collectionSchemaViewChrome-toggler { -// width: 100px; -// height: 35px; -// background-color: $black; -// position: relative; -// } - -// .collectionSchemaViewChrome-togglerButton { -// width: 47px; -// height: 30px; -// background-color: $light-gray; -// // position: absolute; -// transition: all 0.5s ease; -// // top: 3px; -// margin-top: 3px; -// color: $medium-gray; -// letter-spacing: 2px; -// text-transform: uppercase; -// display: flex; -// flex-direction: column; -// justify-content: center; -// text-align: center; - -// &.on { -// margin-left: 3px; -// } - -// &.off { -// margin-left: 50px; -// } -// } -// } - - -// .commandEntry-outerDiv { -// display: flex; -// flex-direction: column; -// height: 40px; -// } - -// .commandEntry-inputArea { -// display: flex; -// flex-direction: row; -// width: 150px; -// margin: auto auto auto auto; -// } - -// .react-autosuggest__container { -// position: relative; -// width: 100%; -// margin-left: 5px; -// margin-right: 5px; -// } - -// .react-autosuggest__input { -// border: 1px solid $light-gray; -// border-radius: 4px; -// width: 100%; -// } - -// .react-autosuggest__input--focused { -// outline: none; -// } - -// .react-autosuggest__input--open { -// border-bottom-left-radius: 0; -// border-bottom-right-radius: 0; -// } - -// .react-autosuggest__suggestions-container { -// display: none; -// } - -// .react-autosuggest__suggestions-container--open { -// display: block; -// position: fixed; -// overflow-y: auto; -// max-height: 400px; -// width: 180px; -// border: 1px solid $light-gray; -// background-color: $white; -// font-family: $sans-serif; -// font-weight: 300; -// font-size: $large-header; -// border-bottom-left-radius: 4px; -// border-bottom-right-radius: 4px; -// z-index: 2; -// } - -// .react-autosuggest__suggestions-list { -// margin: 0; -// padding: 0; -// list-style-type: none; -// } - -// .react-autosuggest__suggestion { -// cursor: pointer; -// padding: 10px 20px; -// } - -// .react-autosuggest__suggestion--highlighted { -// background-color: $light-gray; -// }
\ No newline at end of file +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index fc3f713ce..9eb716763 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -2,23 +2,23 @@ import React = require('react'); import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, Lambda, observable, reaction, runInAction } from 'mobx'; +import { Toggle, ToggleType, Type } from 'browndash-components'; +import { Lambda, action, computed, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState } from 'react-color'; +import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; -import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { Document } from '../../../fields/documentSchemas'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils'; -import { Docs } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; @@ -28,20 +28,19 @@ import { undoBatch } from '../../util/UndoManager'; import { AntimodeMenu } from '../AntimodeMenu'; import { EditableView } from '../EditableView'; import { GestureOverlay } from '../GestureOverlay'; -import { Colors } from '../global/globalEnums'; import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke'; import { LightboxView } from '../LightboxView'; +import { MainView } from '../MainView'; +import { DefaultStyleProvider } from '../StyleProvider'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocumentView, OpenWhereMod } from '../nodes/DocumentView'; -import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { DefaultStyleProvider } from '../StyleProvider'; import { CollectionDockingView } from './CollectionDockingView'; -import { CollectionLinearView } from './collectionLinear'; import './CollectionMenu.scss'; import { COLLECTION_BORDER_WIDTH } from './CollectionView'; import { TabDocView } from './TabDocView'; import { CollectionFreeFormView } from './collectionFreeForm'; +import { CollectionLinearView } from './collectionLinear'; interface CollectionMenuProps { panelHeight: () => number; @@ -95,6 +94,15 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { } }; + @action + toggleProperties = () => { + if (MainView.Instance.propertiesWidth() > 0) { + SettingsManager.propertiesWidth = 0; + } else { + SettingsManager.propertiesWidth = 300; + } + }; + buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current); @@ -139,23 +147,44 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { } render() { - const propIcon = SettingsManager.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down'; - const propTitle = SettingsManager.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar'; - - const prop = ( - <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="topar" placement="bottom"> - <div className="collectionMenu-hardCodedButton" style={{ backgroundColor: SettingsManager.propertiesWidth > 0 ? Colors.MEDIUM_BLUE : undefined }} onPointerDown={this.toggleTopBar}> - <FontAwesomeIcon icon={propIcon} size="lg" /> - </div> - </Tooltip> + const headerIcon = SettingsManager.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down'; + const headerTitle = SettingsManager.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar'; + const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; + const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties'; + + const hardCodedButtons = ( + <div className={`hardCodedButtons`}> + <Toggle + toggleType={ToggleType.BUTTON} + type={Type.PRIM} + color={StrCast(Doc.UserDoc().userColor)} + onClick={this.toggleTopBar} + toggleStatus={SettingsManager.headerBarHeight > 0} + icon={<FontAwesomeIcon icon={headerIcon} size="lg" />} + tooltip={headerTitle} + /> + <Toggle + toggleType={ToggleType.BUTTON} + type={Type.PRIM} + color={StrCast(Doc.UserDoc().userColor)} + onClick={this.toggleProperties} + toggleStatus={SettingsManager.propertiesWidth > 0} + icon={<FontAwesomeIcon icon={propIcon} size="lg" />} + tooltip={propTitle} + /> + </div> ); // NEW BUTTONS //dash col linear view buttons const contMenuButtons = ( - <div className="collectionMenu-container"> + <div className="collectionMenu-container" + style={{ + background: StrCast(Doc.UserDoc().userBackgroundColor), + // borderColor: StrCast(Doc.UserDoc().userColor) + }} > {this.contMenuButtons} - {prop} + {hardCodedButtons} </div> ); @@ -427,8 +456,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu if (docDragData?.draggedDocuments.length) { this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || [])); e.stopPropagation(); + return true; } - return true; + return false; } dragViewDown = (e: React.PointerEvent) => { diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 675f23970..53a42d2a6 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -10,7 +10,7 @@ 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 { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils'; +import { emptyFunction, returnFalse, returnZero, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; @@ -47,7 +47,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { @observable _cursor: CursorProperty = 'grab'; @observable _scroll = 0; @computed get chromeHidden() { - return BoolCast(this.layoutDoc.chromeHidden); + return BoolCast(this.layoutDoc.chromeHidden) || this.props.onBrowseClick?.() ? true : false; } // columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns @computed get colHeaderData() { @@ -192,7 +192,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { if (found) { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); - if (Math.floor(localTop[1]) !== 0) { + if (Math.floor(localTop[1]) !== 0 && Math.ceil(this.props.PanelHeight()) < (this._mainCont?.scrollHeight || 0)) { let focusSpeed = options.zoomTime ?? 500; smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); return focusSpeed; @@ -250,8 +250,9 @@ export class CollectionNoteTakingView extends CollectionSubView() { dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} rootSelected={this.rootSelected} layout_showTitle={this.props.childlayout_showTitle} - dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} + dragAction={StrCast(this.layoutDoc.childDragAction) as dropActionType} onClick={this.onChildClickHandler} + onBrowseClick={this.props.onBrowseClick} onDoubleClick={this.onChildDoubleClickHandler} ScreenToLocalTransform={noteTakingDocTransform} focus={this.focusDocument} @@ -441,23 +442,24 @@ export class CollectionNoteTakingView extends CollectionSubView() { docs.splice(previousDocIndex + 1, 0, ...newDocs); } } + return true; } } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' }); - this.props.addDocument?.(source); + if (!this.props.addDocument?.(source)) e.preventDefault(); de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed e.stopPropagation(); - } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); + return true; + } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) { + return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); + } return false; }; @undoBatch internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) { const dropCreator = annoDragData.dropDocCreator; - annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => { - const dropDoc = dropCreator(annotationOn); - return dropDoc || this.rootDoc; - }; + annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => dropCreator(annotationOn) || this.rootDoc; return true; } @@ -619,7 +621,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { } @computed get backgroundEvents() { - return SnappingManager.GetIsDragging(); + return this.props.isContentActive() === false ? 'none' : undefined; } observer: any; diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index 2f28ecd00..3286d60bd 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -92,6 +92,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu columnDrop = action((e: Event, de: DragManager.DropEvent) => { 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)); + return true; }); getValue = (value: string): any => { diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 57d9bbb49..91be31289 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -52,20 +52,25 @@ export class CollectionPileView extends CollectionSubView() { @computed get toggleIcon() { return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' }); } + @computed get contentEvents() { + const isStarburst = this.layoutEngine() === computeStarburstLayout.name; + return this.props.isContentActive() && isStarburst ? undefined : 'none'; + } // returns the contents of the pileup in a CollectionFreeFormView @computed get contents() { - const isStarburst = this.layoutEngine() === computeStarburstLayout.name; return ( - <div className="collectionPileView-innards" style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : 'none' }}> + <div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}> <CollectionFreeFormView - {...this.props} - childContentsActive={returnFalse} + {...this.props} // layoutEngine={this.layoutEngine} addDocument={this.addPileDoc} - childCanEmbedOnDrag={true} - childClickScript={this.toggleIcon} moveDocument={this.removePileDoc} + // pile children never have their contents active, but will be document active whenever the entire pile is. + childContentsActive={returnFalse} + childDocumentsActive={this.props.isDocumentActive} + childDragAction="move" + childClickScript={this.toggleIcon} /> </div> ); @@ -103,7 +108,6 @@ export class CollectionPileView extends CollectionSubView() { _undoBatch: UndoManager.Batch | undefined; pointerDown = (e: React.PointerEvent) => { let dist = 0; - SnappingManager.SetIsDragging(true); setupMoveUpEvents( this, e, @@ -126,7 +130,6 @@ export class CollectionPileView extends CollectionSubView() { () => { this._undoBatch?.end(); this._undoBatch = undefined; - SnappingManager.SetIsDragging(false); }, emptyFunction, e.shiftKey && this.layoutEngine() === computePassLayout.name, diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index a55b70e22..a19d8e696 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -104,7 +104,7 @@ .collectionStackedTimeline-left-resizer, .collectionStackedTimeline-resizer { - background: $medium-gray; + background: $dark-gray; position: absolute; top: 0; height: 100%; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 9d5cb257a..ad84d859d 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -119,8 +119,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack // onClick play scripts CollectionStackedTimeline.RangeScript = CollectionStackedTimeline.RangeScript || - ScriptField.MakeFunction(`setTimeout(() => scriptContext.clickAnchor(this, clientX))`, { - // setTimeout is a hack to run script in its own properly named undo group (instead of being part of the generic onClick) + ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { self: Doc.name, scriptContext: 'any', clientX: 'number', @@ -370,22 +369,22 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack // handles dragging and dropping markers in timeline @action internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) { - if (!de.embedKey && this.props.Document._isGroup) return false; - if (!super.onInternalDrop(e, de)) return false; - - // determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view - const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); - const x = localPt[0] - docDragData.offset[0]; - const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth); - docDragData.droppedDocuments.forEach(drop => { - const anchorEnd = this.anchorEnd(drop); - if (anchorEnd !== undefined) { - Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false); - } - Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false); - }); + if (super.onInternalDrop(e, de)) { + // determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view + const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); + const x = localPt[0] - docDragData.offset[0]; + const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth); + docDragData.droppedDocuments.forEach(drop => { + const anchorEnd = this.anchorEnd(drop); + if (anchorEnd !== undefined) { + Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false); + } + Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false); + }); - return true; + return true; + } + return false; } onInternalDrop = (e: Event, de: DragManager.DropEvent) => { @@ -406,7 +405,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack title: ComputedField.MakeFunction(`self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])`) as any, _label_minFontSize: 12, _label_maxFontSize: 24, - _stayInCollection: true, + _dragOnlyWithinContainer: true, backgroundColor: 'rgba(128, 128, 128, 0.5)', layout_hideLinkButton: true, onClick: FollowLinkScript(), @@ -528,6 +527,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack ); } + @computed get timelineEvents() { + return this.props.isContentActive() ? 'all' : this.props.isContentActive() === false ? 'none' : undefined; + } render() { const overlaps: { anchorStartTime: number; @@ -540,7 +542,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; return this.clipDuration === 0 ? null : ( - <div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : undefined }}> + <div ref={this.createDashEventsTarget} style={{ pointerEvents: this.timelineEvents }}> <div className="collectionStackedTimeline-timelineContainer" style={{ width: this.props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 3c0d8cbc3..805002452 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -17,7 +17,7 @@ import { CollectionViewType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; -import { undoBatch } from '../../util/UndoManager'; +import { undoBatch, UndoManager } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; @@ -31,6 +31,7 @@ import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; import './CollectionStackingView.scss'; import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn'; import { CollectionSubView } from './CollectionSubView'; +import { Colors } from '../global/globalEnums'; const _global = (window /* browser */ || global) /* node */ as any; export type collectionStackingViewProps = { @@ -84,7 +85,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } @computed get xMargin() { - return NumCast(this.layoutDoc._xMargin, Math.min(3, 0.05 * this.props.PanelWidth())); + return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this.props.PanelWidth())); } @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this.props.PanelWidth())); @@ -295,7 +296,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return this.addDocument?.(newDoc); } }; - isContentActive = () => (this.props.isSelected() || this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined); + isContentActive = () => (this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined); @observable _renderCount = 5; isChildContentActive = () => @@ -341,7 +342,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to. rootSelected={this.rootSelected} layout_showTitle={this.props.childlayout_showTitle} - dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} + dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType} onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} ScreenToLocalTransform={stackedDocTransform} @@ -402,11 +403,15 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // This following three functions must be from the view Mehek showed columnDividerDown = (e: React.PointerEvent) => { runInAction(() => (this._cursor = 'grabbing')); + const batch = UndoManager.StartBatch('stacking width'); setupMoveUpEvents( this, e, this.onDividerMove, - action(() => (this._cursor = 'grab')), + action(() => { + this._cursor = 'grab'; + batch.end(); + }), emptyFunction ); }; @@ -455,18 +460,24 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const docs = this.childDocList; // still figuring out where to add the document if (docs && newDocs.length) { + newDocs.forEach(newdoc => docs.indexOf(newdoc) !== -1 && docs.splice(docs.indexOf(newdoc), 1)); const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter; const offset = newDocs.reduce((off, ndoc) => (this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off), 0); newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1)); docs.splice(insertInd - offset, 0, ...newDocs); } + return true; } } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' }); - this.props.addDocument?.(source); + if (!this.props.addDocument?.(source)) e.preventDefault(); de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed e.stopPropagation(); - } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); + return true; + } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) { + return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); + } + e.preventDefault(); return false; }; @@ -700,7 +711,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } @computed get backgroundEvents() { - return SnappingManager.GetIsDragging(); + return this.props.isContentActive() === false ? 'none' : undefined; } observer: any; render() { @@ -710,7 +721,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection SetValue: this.addGroup, contents: '+ ADD A GROUP', }; - const buttonMenu = this.rootDoc.buttonMenu; + const buttonMenu = this.rootDoc.layout_headerButton; const noviceExplainer = this.rootDoc.layout_explainer; return ( <> @@ -727,7 +738,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection style={{ overflowY: this.isContentActive() ? 'auto' : 'hidden', background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor), - pointerEvents: (this.props.pointerEvents?.() as any) ?? (this.backgroundEvents ? 'all' : undefined), + pointerEvents: (this.props.pointerEvents?.() as any) ?? this.backgroundEvents, }} onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))} onDrop={this.onExternalDrop.bind(this)} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 6be9cb72d..ebb4ba5a1 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -94,6 +94,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC columnDrop = action((e: Event, de: DragManager.DropEvent) => { const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) }; this.props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false)); + return true; }); getValue = (value: string): any => { const parsed = parseInt(value); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d1b7f6ff6..c189ef126 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -185,12 +185,14 @@ export function CollectionSubView<X>(moreProps?: X) { @undoBatch protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {} - protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetAction: dropActionType) { + protected onInternalPreDrop(e: Event, de: DragManager.DropEvent) { if (de.complete.docDragData) { - // if targetDropAction is, say 'embed', but we're just dragging within a collection, we want to ignore the targetAction. - // otherwise, the targetAction should become the actual action (which can still be overridden by the userDropAction -eg, shift/ctrl keys) - if (targetAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.props.Document && this.childDocs.includes(d))) { - de.complete.docDragData.dropAction = targetAction; + // override the dropEvent's dropAction + const dropAction = this.layoutDoc.dropAction as dropActionType; + // 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.props.Document && this.childDocs.includes(d))) { + de.complete.docDragData.dropAction = dropAction; } e.stopPropagation(); } @@ -203,7 +205,7 @@ export function CollectionSubView<X>(moreProps?: X) { protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { const docDragData = de.complete.docDragData; if (docDragData) { - let added = false; + let added = undefined; const dropAction = docDragData.dropAction || docDragData.userDropAction; const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]); const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag)); @@ -212,21 +214,21 @@ export function CollectionSubView<X>(moreProps?: X) { const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d); const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d); if (movedDocs.length) { - const canAdd = this.props.Document._type_collection === CollectionViewType.Pile || de.embedKey || this.props.Document.allowOverlayDrop || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document); - added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse); + const canAdd = de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.rootDoc); + const moved = docDragData.moveDocument(movedDocs, this.rootDoc, canAdd ? this.addDocument : returnFalse); + added = canAdd || moved ? moved : undefined; } else { - ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData }); + ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData }); added = addedDocs.length ? this.addDocument(addedDocs) : true; } - added && e.stopPropagation(); - return added; } else { - ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData }); + ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData }); added = this.addDocument(docDragData.droppedDocuments); + !added && alert('You cannot perform this move'); } - !added && alert('You cannot perform this move'); - e.stopPropagation(); - return added; + added === false && !this.props.isAnnotationOverlay && e.preventDefault(); + added === true && e.stopPropagation(); + return added ? true : false; } else if (de.complete.annoDragData) { const dropCreator = de.complete.annoDragData.dropDocCreator; de.complete.annoDragData.dropDocCreator = () => { diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 60e6815e5..192d48c64 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -34,8 +34,6 @@ export class CollectionTimeView extends CollectionSubView() { async componentDidMount() { this.props.setContentView?.(this); - //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), ""); - ///const childText = "const embedding = getEmbedding(self); switchView(embedding, detailView); embedding.dropAction='embed'; useRightSplit(embedding, shiftKey); "; runInAction(() => { this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name }); this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' }); diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 273b08247..2bf649caf 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -13,7 +13,7 @@ width: 100%; position: relative; top: 0; - background: $light-gray; + // background: $light-gray; font-size: 13px; overflow: auto; user-select: none; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ed1e0c067..00137736d 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { DocData, Height, Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; -import { InkTool } from '../../../fields/InkField'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; @@ -90,8 +89,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent @observable _isAnyChildContentActive = false; whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive))); - isContentActive = (outsideReaction?: boolean) => - Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || this.props.rootSelected(outsideReaction) ? true : false; + isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this.props.isContentActive() ? true : false); componentWillUnmount() { this._isDisposing = true; @@ -142,11 +140,16 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this)); }; - protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { + protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent) => { + const dropAction = this.layoutDoc.dropAction as dropActionType; const dragData = de.complete.docDragData; if (dragData) { - const isInTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.props.Document) || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d)); - dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? 'same' : dragData.dropAction; + const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.rootDoc) ? true : false; + const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d)); + if (isAlreadyInTree() !== sameTree) { + console.log('WHAAAT'); + } + dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree ? 'same' : dragData.dropAction; } }; @@ -177,7 +180,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree const doAddDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => { const res = flg && Doc.AddDocToList(this.doc[DocData], this.props.fieldKey, doc, relativeTo, before); - res && (doc.embedContainer = this.props.Document); + res && Doc.SetContainer(doc, this.props.Document); return res; }, true); if (this.doc.resolvedDataDoc instanceof Promise) return false; @@ -261,7 +264,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree @observable _renderCount = 1; @computed get treeViewElements() { TraceMobx(); - const dropAction = StrCast(this.doc.childDropAction) as dropActionType; + const dragAction = StrCast(this.doc.childDragAction) as dropActionType; const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before); const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false; if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20)))); @@ -276,7 +279,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree addDoc, this.remove, moveDoc, - dropAction, + dragAction, this.props.addDocTab, this.props.styleProvider, this.screenToLocalTransform, @@ -383,7 +386,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree @observable _headerHeight = 0; @computed get content() { const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor); - const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined); + const color = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.Color); + const pointerEvents = () => (this.props.isContentActive() === false ? 'none' : undefined); const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? null : this.titleBar; return ( <div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}> @@ -396,8 +400,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree <div className="collectionTreeView-contents" key="tree" + ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)} style={{ ...(!titleBar ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}), + color: color(), overflow: 'auto', width: '100%', height: '100%', @@ -419,8 +425,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree minHeight: '100%', }} onWheel={e => e.stopPropagation()} - onDrop={this.onTreeDrop} - ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}> + onDrop={this.onTreeDrop}> <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul> </div> </div> @@ -440,12 +445,12 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree setContentView={emptyFunction} NativeWidth={returnZero} NativeHeight={returnZero} - pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} + pointerEvents={this.props.isContentActive() && SnappingManager.GetIsDragging() ? returnAll : returnNone} isAnnotationOverlay={true} isAnnotationOverlayScrollable={true} childDocumentsActive={this.props.isDocumentActive} fieldKey={this.props.fieldKey + '_annotations'} - dropAction={'move'} + dropAction="move" select={emptyFunction} addDocument={this.addAnnotationDocument} removeDocument={this.remAnnotationDocument} diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 7913d3188..c33519afb 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -9,6 +9,7 @@ import { TraceMobx } from '../../../fields/util'; import { returnEmptyString } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; +import { dropActionType } from '../../util/DragManager'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { InteractionUtils } from '../../util/InteractionUtils'; import { ContextMenu } from '../ContextMenu'; @@ -55,7 +56,7 @@ interface CollectionViewProps_ extends FieldViewProps { childHideDecorationTitle?: () => boolean; childHideResizeHandles?: () => boolean; childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection - childCanEmbedOnDrag?: boolean; + childDragAction?: dropActionType; childXPadding?: number; childYPadding?: number; childLayoutString?: string; @@ -220,7 +221,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles); childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle); childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null); - isContentActive = (outsideReaction?: boolean) => this.props.isContentActive() || this.isAnyChildContentActive(); + isContentActive = (outsideReaction?: boolean) => (this.isAnyChildContentActive() ? true : this.props.isContentActive()); render() { TraceMobx(); diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss index 58605c3f4..13bb3a577 100644 --- a/src/client/views/collections/TabDocView.scss +++ b/src/client/views/collections/TabDocView.scss @@ -65,11 +65,7 @@ input.lm_title { } .miniMap { - position: absolute; overflow: hidden; - right: 15; - bottom: 15; - border: solid 1px; width: 100%; height: 100%; transition: all 0.5s; @@ -90,18 +86,5 @@ input.lm_title { cursor: pointer; position: absolute; bottom: 5; - display: flex; right: 5; - width: 25px; - height: 25px; - border-radius: 3px; - padding: 2px; - justify-content: center; - align-items: center; - align-content: center; - background-color: $light-gray; - - &:hover { - box-shadow: none; - } } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 75e4e8abf..b9f13b188 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { clamp } from 'lodash'; -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableSet, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom/client'; import { Doc, Opt } from '../../../fields/Doc'; @@ -34,6 +34,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV import { CollectionView } from './CollectionView'; import './TabDocView.scss'; import React = require('react'); +import { Popup, Toggle, Type } from 'browndash-components'; const _global = (window /* browser */ || global) /* node */ as any; interface TabDocViewProps { @@ -43,12 +44,21 @@ interface TabDocViewProps { } @observer export class TabDocView extends React.Component<TabDocViewProps> { + static _allTabs = new ObservableSet<TabDocView>(); _mainCont: HTMLDivElement | null = null; _tabReaction: IReactionDisposer | undefined; @observable _activated: boolean = false; @observable _panelWidth = 0; @observable _panelHeight = 0; + @observable _hovering = false; @observable _isActive: boolean = false; + @observable _isAnyChildContentActive = false; + @computed get _isUserActivated() { + return SelectionManager.Views().some(view => view.rootDoc === this._document) || this._isAnyChildContentActive; + } + @computed get _isContentActive() { + return this._isUserActivated || this._hovering; + } @observable _document: Doc | undefined; @observable _view: DocumentView | undefined; @@ -61,9 +71,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { return 'transparent'; } @computed get tabColor() { - let tabColor = StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); - if (tabColor === 'transparent') return 'black'; - return tabColor; + return this._isUserActivated ? Colors.WHITE : this._hovering ? Colors.LIGHT_GRAY : Colors.MEDIUM_GRAY; } @computed get tabTextColor() { return this._document?.type === DocumentType.PRES ? 'black' : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color))); @@ -191,7 +199,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { } }); tab._disposers.selectionDisposer = reaction( - () => SelectionManager.Views().some(v => v.topMost && v.props.Document === doc), + () => SelectionManager.Views().some(view => view.rootDoc === this._document), action(selected => { if (selected) this._activated = true; const toggle = tab.element[0].children[2].children[0] as HTMLInputElement; @@ -255,7 +263,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { pinDoc.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom; pinDoc.presDuration = pinDoc.presDuration ?? 1000; pinDoc.groupWithUp = false; - pinDoc.embedContainer = curPres; + Doc.SetContainer(pinDoc, curPres); // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area pinDoc.treeViewHeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling. @@ -305,6 +313,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs } + @action componentDidMount() { new _global.ResizeObserver( action((entries: any) => { @@ -319,14 +328,17 @@ export class TabDocView extends React.Component<TabDocViewProps> { // this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }), // ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""), // { fireImmediately: true }); + TabDocView._allTabs.add(this); } componentDidUpdate() { this._view && DocumentManager.Instance.AddView(this._view); } + @action componentWillUnmount() { this._tabReaction?.(); this._view && DocumentManager.Instance.RemoveView(this._view); + TabDocView._allTabs.delete(this); this.props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged); } @@ -335,6 +347,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { private onActiveContentItemChanged(contentItem: any) { if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; + if (!this._view) setTimeout(() => SelectionManager.SelectView(this._view, false)); !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. } } @@ -348,10 +361,10 @@ export class TabDocView extends React.Component<TabDocViewProps> { // lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox addDocTab = (doc: Doc, location: OpenWhere) => { SelectionManager.DeselectAll(); - const whereFields = doc._type_collection === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); + const whereFields = location.split(':'); const keyValue = whereFields[1]?.includes('KeyValue'); const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none; - if (doc.dockingConfig) return DashboardView.openDashboard(doc); + if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc); // prettier-ignore switch (whereFields[0]) { case undefined: @@ -364,7 +377,6 @@ export class TabDocView extends React.Component<TabDocViewProps> { } } return LightboxView.AddDocTab(doc, location); - case OpenWhere.dashboard: return DashboardView.openDashboard(doc); case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, undefined, keyValue); @@ -405,11 +417,11 @@ export class TabDocView extends React.Component<TabDocViewProps> { }; PanelWidth = () => this._panelWidth; PanelHeight = () => this._panelHeight; - miniMapColor = () => this.tabColor; + miniMapColor = () => Colors.MEDIUM_GRAY; tabView = () => this._view; disableMinimap = () => !this._document || this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._type_collection !== CollectionViewType.Freeform; - hideMinimap = () => this.disableMinimap() || BoolCast(this._document?.layout_hideMinimap); - + whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive); + isContentActive = () => this._isContentActive; @computed get docView() { return !this._activated || !this._document ? null : ( <> @@ -427,7 +439,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { DataDoc={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined} onBrowseClick={MainView.Instance.exploreMode} waitForDoubleClickToClick={MainView.Instance.waitForDoubleClick} - isContentActive={returnTrue} + isContentActive={this.isContentActive} isDocumentActive={returnFalse} PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight} @@ -441,30 +453,15 @@ export class TabDocView extends React.Component<TabDocViewProps> { ScreenToLocalTransform={this.ScreenToLocalTransform} dontCenter={'y'} rootSelected={returnTrue} - whenChildContentsActiveChanged={emptyFunction} + whenChildContentsActiveChanged={this.whenChildContentActiveChanges} focus={this.focusFunc} docViewPath={returnEmptyDoclist} bringToFront={emptyFunction} pinToPres={TabDocView.PinDoc} /> - <TabMinimapView key="minimap" hideMinimap={this.hideMinimap} addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} /> - <Tooltip key="ttip" title={<div className="dash-tooltip">{this._document.layout_hideMinimap ? 'Open minimap' : 'Close minimap'}</div>}> - <div - className="miniMap-hidden" - style={{ - display: this.disableMinimap() || this._document._type_collection !== 'freeform' ? 'none' : undefined, - color: this._document.layout_hideMinimap ? Colors.BLACK : Colors.WHITE, - backgroundColor: this._document.layout_hideMinimap ? Colors.LIGHT_GRAY : Colors.MEDIUM_BLUE, - boxShadow: this._document.layout_hideMinimap ? Shadows.STANDARD_SHADOW : undefined, - }} - onPointerDown={e => e.stopPropagation()} - onClick={action(e => { - e.stopPropagation(); - this._document!.layout_hideMinimap = !this._document!.layout_hideMinimap; - })}> - <FontAwesomeIcon icon={'globe-asia'} size="lg" /> - </div> - </Tooltip> + {this.disableMinimap() || this._document._type_collection !== CollectionViewType.Freeform ? null : ( + <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} /> + )} </> ); } @@ -476,6 +473,10 @@ export class TabDocView extends React.Component<TabDocViewProps> { style={{ fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined, }} + onPointerEnter={action(() => (this._hovering = true))} + onPointerLeave={action(() => (this._hovering = false))} + onDragOver={action(() => (this._hovering = true))} + onDragLeave={action(() => (this._hovering = false))} ref={ref => { if ((this._mainCont = ref)) { if (this._lastTab) { @@ -495,7 +496,6 @@ export class TabDocView extends React.Component<TabDocViewProps> { interface TabMinimapViewProps { document: Doc; - hideMinimap: () => boolean; tabView: () => DocumentView | undefined; addDocTab: (doc: Doc, where: OpenWhere) => boolean; PanelWidth: () => number; @@ -570,46 +570,56 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { if (!this.renderBounds) return null; const miniWidth = (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100; const miniHeight = (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100; - const miniLeft = 50 + ((NumCast(this.props.document._freeform_) - this.renderBounds.cx) / this.renderBounds.dim) * 100 - miniWidth / 2; + const miniLeft = 50 + ((NumCast(this.props.document._freeform_panX) - this.renderBounds.cx) / this.renderBounds.dim) * 100 - miniWidth / 2; const miniTop = 50 + ((NumCast(this.props.document._freeform_panY) - this.renderBounds.cy) / this.renderBounds.dim) * 100 - miniHeight / 2; const miniSize = this.returnMiniSize(); - return this.props.hideMinimap() ? null : ( - <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}> - <CollectionFreeFormView - Document={this.props.document} - docViewPath={returnEmptyDoclist} - childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this. - noOverlay={true} // don't render overlay Docs since they won't scale - setHeight={returnFalse} - isContentActive={emptyFunction} - isAnyChildContentActive={returnFalse} - select={emptyFunction} - dropAction={undefined} - isSelected={returnFalse} - dontRegisterView={true} - fieldKey={Doc.LayoutFieldKey(this.props.document)} - bringToFront={emptyFunction} - rootSelected={returnTrue} - addDocument={returnFalse} - moveDocument={returnFalse} - removeDocument={returnFalse} - PanelWidth={this.returnMiniSize} - PanelHeight={this.returnMiniSize} - ScreenToLocalTransform={Transform.Identity} - renderDepth={0} - whenChildContentsActiveChanged={emptyFunction} - focus={emptyFunction} - styleProvider={TabMinimapView.miniStyleProvider} - addDocTab={this.props.addDocTab} - pinToPres={TabDocView.PinDoc} - childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist} - childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist} - searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} - fitContentsToBox={returnTrue} + return ( + <div className="miniMap-hidden"> + <Popup + icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} + color={StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE)} + type={Type.TERT} + onPointerDown={e => e.stopPropagation()} + placement={'top-end'} + popup={ + <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}> + <CollectionFreeFormView + Document={this.props.document} + docViewPath={returnEmptyDoclist} + childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this. + noOverlay={true} // don't render overlay Docs since they won't scale + setHeight={returnFalse} + isContentActive={emptyFunction} + isAnyChildContentActive={returnFalse} + select={emptyFunction} + isSelected={returnFalse} + dontRegisterView={true} + fieldKey={Doc.LayoutFieldKey(this.props.document)} + bringToFront={emptyFunction} + rootSelected={returnTrue} + addDocument={returnFalse} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelWidth={this.returnMiniSize} + PanelHeight={this.returnMiniSize} + ScreenToLocalTransform={Transform.Identity} + renderDepth={0} + whenChildContentsActiveChanged={emptyFunction} + focus={emptyFunction} + styleProvider={TabMinimapView.miniStyleProvider} + addDocTab={this.props.addDocTab} + pinToPres={TabDocView.PinDoc} + childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist} + childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist} + searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} + fitContentsToBox={returnTrue} + /> + <div className="miniOverlay" onPointerDown={this.miniDown}> + <div className="miniThumb" style={{ width: `${miniWidth}% `, height: `${miniHeight}% `, left: `${miniLeft}% `, top: `${miniTop}% ` }} /> + </div> + </div> + } /> - <div className="miniOverlay" onPointerDown={this.miniDown}> - <div className="miniThumb" style={{ width: `${miniWidth}% `, height: `${miniHeight}% `, left: `${miniLeft}% `, top: `${miniTop}% ` }} /> - </div> </div> ); } diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 7eab03e1d..cfa658944 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -13,7 +13,7 @@ .treeView-container-active { .bullet-outline { position: relative; - width: $TREE_BULLET_WIDTH; + width: fit-content; color: $medium-gray; transform: scale(0.5); display: inline-flex; @@ -21,37 +21,20 @@ } .treeView-bulletIcons { - // width: $TREE_BULLET_WIDTH; width: 100%; height: 100%; - position: absolute; - - .treeView-expandIcon { - display: none; - left: -10px; - position: absolute; - } - - .treeView-checkIcon { - left: 3.5px; - top: 2px; - position: absolute; - } - - &:hover { - .treeView-expandIcon { - display: unset; - } - } + position: relative; + display: flex; + flex-direction: row; } - .treeView-bulletIcons:hover img { - left: 14px; - position: absolute; - transform-origin: center left; - transform: scale(6); - pointer-events: none; - } + // .treeView-bulletIcons:hover img { + // left: 14px; + // position: absolute; + // transform-origin: center left; + // transform: scale(6); + // pointer-events: none; + // } .bullet { grid-column: 1; @@ -59,7 +42,7 @@ justify-content: center; align-items: center; position: relative; - width: $TREE_BULLET_WIDTH; + width: fit-content; min-height: 20px; color: $medium-gray; border: #80808030 1px solid; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 6bc30c451..9158508fc 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -21,7 +21,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; -import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss'; import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView'; @@ -34,6 +34,7 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; import { CollectionView } from './CollectionView'; import './TreeView.scss'; import React = require('react'); +import { IconButton, Size } from 'browndash-components'; export interface TreeViewProps { treeView: CollectionTreeView; @@ -45,7 +46,7 @@ export interface TreeViewProps { dataDoc?: Doc; treeViewParent: Doc; renderDepth: number; - dropAction: dropActionType; + dragAction: dropActionType; addDocTab: (doc: Doc, where: OpenWhere) => boolean; panelWidth: () => number; panelHeight: () => number; @@ -221,12 +222,10 @@ export class TreeView extends React.Component<TreeViewProps> { this.treeViewOpen = !this.treeViewOpen; } else { // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding - const bestEmbedding = - docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) - ? docView.props.Document - : DocListCast(this.props.document.proto_embeddings).find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail); - const nextBestEmbedding = DocListCast(this.props.document.proto_embeddings).find(doc => doc.author === Doc.CurrentUserEmail); - this.props.addDocTab(bestEmbedding ?? nextBestEmbedding ?? Doc.MakeEmbedding(this.props.document), OpenWhere.lightbox); + const bestEmbedding = docView.rootDoc.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) + ? docView.rootDoc + : Doc.BestEmbedding(docView.rootDoc); + this.props.addDocTab(bestEmbedding, OpenWhere.lightbox); } }; @@ -276,7 +275,7 @@ export class TreeView extends React.Component<TreeViewProps> { }; onPointerEnter = (e: React.PointerEvent): void => { this.props.isContentActive(true) && Doc.BrushDoc(this.dataDoc); - if (e.buttons === 1 && SnappingManager.GetIsDragging()) { + if (e.buttons === 1 && SnappingManager.GetIsDragging() && this.props.isContentActive()) { this._header.current!.className = 'treeView-header'; document.removeEventListener('pointermove', this.onDragMove, true); document.removeEventListener('pointerup', this.onDragUp, true); @@ -341,13 +340,13 @@ export class TreeView extends React.Component<TreeViewProps> { }; makeFolder = () => { - const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true }); + const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }); TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView }; return this.props.addDocument(folder); }; deleteItem = () => this.props.removeDoc?.(this.doc); - preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { + preTreeDrop = (e: Event, de: DragManager.DropEvent) => { const dragData = de.complete.docDragData; dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction); }; @@ -355,7 +354,7 @@ export class TreeView extends React.Component<TreeViewProps> { @undoBatch treeDrop = (e: Event, de: DragManager.DropEvent) => { const pt = [de.x, de.y]; - if (!this._header.current) return; + if (!this._header.current) return false; const rect = this._header.current.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); @@ -364,14 +363,25 @@ export class TreeView extends React.Component<TreeViewProps> { const destDoc = this.doc; DocUtils.MakeLink(sourceDoc, destDoc, { link_relationship: 'tree link' }); e.stopPropagation(); + return true; } const docDragData = de.complete.docDragData; if (docDragData && pt[0] < rect.left + rect.width) { if (docDragData.draggedDocuments[0] === this.doc) return true; - if (this.dropDocuments(docDragData.droppedDocuments, before, inside, docDragData.dropAction, docDragData.removeDocument, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document)) { - e.stopPropagation(); - } + const added = this.dropDocuments( + docDragData.droppedDocuments, // + before, + inside, + docDragData.dropAction, + docDragData.removeDocument, + docDragData.moveDocument, + docDragData.treeViewDoc === this.props.treeView.props.Document + ); + e.stopPropagation(); + !added && e.preventDefault(); + return added; } + return false; }; dropping: boolean = false; @@ -381,7 +391,7 @@ export class TreeView extends React.Component<TreeViewProps> { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); - dataIsComputed && (doc.embedContainer = this.doc.embedContainer); + dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer)); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); @@ -391,7 +401,7 @@ export class TreeView extends React.Component<TreeViewProps> { const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeViewFreezeChildren).includes('add')) || forceAdd; if (canAdd) { this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true); - const res = UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false)); + const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false); this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = false); return res; } @@ -444,7 +454,7 @@ export class TreeView extends React.Component<TreeViewProps> { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - dataIsComputed && (doc.embedContainer = this.doc.embedContainer); + dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer)); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); @@ -460,7 +470,7 @@ export class TreeView extends React.Component<TreeViewProps> { addDoc, remDoc, moveDoc, - this.props.dropAction, + this.props.dragAction, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, @@ -530,6 +540,8 @@ export class TreeView extends React.Component<TreeViewProps> { TraceMobx(); const expandKey = this.treeViewExpandedView; const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }) ?? {}; + const color = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Color) + console.log("tree view", color, this.doc.title, Doc.IsSystem(this.doc)) if (['links', 'annotations', 'embeddings', this.fieldKey].includes(expandKey)) { const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); const sortKeys = Object.keys(sortings); @@ -552,7 +564,7 @@ export class TreeView extends React.Component<TreeViewProps> { } const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - !dataIsComputed && added && (doc.embedContainer = this.doc.embedContainer); + !dataIsComputed && added && Doc.SetContainer(doc, DocCast(this.doc.embedContainer)); return added; }; @@ -569,7 +581,9 @@ export class TreeView extends React.Component<TreeViewProps> { ); } return ( - <> + <div style={{ + color: color + }}> {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : ( <div className={'treeView-sorting'} style={{ background: sortings[sorting]?.color }}> {sortings[sorting]?.label} @@ -585,12 +599,12 @@ export class TreeView extends React.Component<TreeViewProps> { downY = e.clientY; e.stopPropagation(); }} - onClick={e => { + onClick={undoable(e => { if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); e.stopPropagation(); } - }}> + }, 'sort order')}> {!docs ? null : TreeView.GetChildElements( @@ -604,7 +618,7 @@ export class TreeView extends React.Component<TreeViewProps> { addDoc, remDoc, moveDoc, - StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, + StrCast(this.doc.childDragAction, this.props.dragAction) as dropActionType, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, @@ -629,17 +643,18 @@ export class TreeView extends React.Component<TreeViewProps> { this._renderCount )} </ul> - </> + </div> ); } else if (this.treeViewExpandedView === 'fields') { return ( - <ul key={this.doc[Id] + this.doc.title} style={{ cursor: 'inherit' }}> + <ul key={this.doc[Id] + this.doc.title} style={{ cursor: 'inherit', color: color }}> <div>{this.expandedField}</div> </ul> ); } return ( <ul + style={{ color: color }} onPointerDown={e => { e.preventDefault(); e.stopPropagation(); @@ -674,6 +689,7 @@ export class TreeView extends React.Component<TreeViewProps> { @computed get renderBullet() { TraceMobx(); const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question'; + const color = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Color) const checked = this.onCheckedClick ? this.doc.treeViewChecked ?? 'unchecked' : undefined; return ( <div @@ -694,14 +710,26 @@ export class TreeView extends React.Component<TreeViewProps> { }> {this.props.treeView.outlineMode ? ( !(this.doc.text as RichTextField)?.Text ? null : ( - <FontAwesomeIcon size="sm" icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} /> + <IconButton + color={color} + icon={<FontAwesomeIcon + icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} />} + size={Size.XSMALL} + /> ) ) : ( <div className="treeView-bulletIcons" style={{ color: Doc.IsSystem(DocCast(this.doc.proto)) ? 'red' : undefined }}> - <div className={`treeView-${this.onCheckedClick ? 'checkIcon' : 'expandIcon'}`}> - <FontAwesomeIcon size="sm" icon={checked === 'check' ? 'check' : checked === 'x' ? 'times' : checked === 'unchecked' ? 'square' : !this.treeViewOpen ? 'caret-right' : 'caret-down'} /> - </div> - {this.onCheckedClick ? null : typeof iconType === 'string' ? <FontAwesomeIcon icon={iconType as IconProp} /> : iconType} + {this.onCheckedClick ? <IconButton + color={color} + icon={<FontAwesomeIcon size="sm" icon={checked === 'check' ? 'check' : checked === 'x' ? 'times' : checked === 'unchecked' ? 'square' : !this.treeViewOpen ? 'caret-right' : 'caret-down'} />} + size={Size.XSMALL} + /> : + <IconButton + color={color} + icon={<FontAwesomeIcon icon={iconType as IconProp} />} + size={Size.XSMALL} + /> + } </div> )} </div> @@ -729,15 +757,15 @@ export class TreeView extends React.Component<TreeViewProps> { @observable headerEleWidth = 0; @computed get titleButtons() { const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations); + const color = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Color); return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : ( <> {customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */} {this.doc._layout_hideContextMenu ? null : ( - <FontAwesomeIcon - title="context menu" - key="bars" - icon="bars" - size="sm" + <IconButton + color={color} + icon={<FontAwesomeIcon icon="bars" />} + size={Size.XSMALL} onClick={e => { this.showContextMenu(e); e.stopPropagation(); @@ -904,7 +932,6 @@ export class TreeView extends React.Component<TreeViewProps> { hideDecorationTitle={this.props.treeView.outlineMode} hideResizeHandles={this.props.treeView.outlineMode} styleProvider={this.titleStyleProvider} - enableDragWhenActive={true} onClickScriptDisable="never" // tree docViews have a script to show fields, etc. docViewPath={this.props.treeView.props.docViewPath} treeViewDoc={this.props.treeView.props.Document} @@ -914,7 +941,7 @@ export class TreeView extends React.Component<TreeViewProps> { pinToPres={emptyFunction} onClick={this.onChildClick} onDoubleClick={this.onChildDoubleClick} - dropAction={this.props.dropAction} + dragAction={this.props.dragAction} moveDocument={this.move} removeDocument={this.props.removeDoc} ScreenToLocalTransform={this.getTransform} @@ -925,7 +952,7 @@ export class TreeView extends React.Component<TreeViewProps> { PanelHeight={return18} contextMenuItems={this.contextMenuItems} renderDepth={1} - isContentActive={this.props.isContentActive} + isContentActive={emptyFunction} //this.props.isContentActive} isDocumentActive={this.props.isContentActive} focus={this.refocus} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} @@ -950,7 +977,7 @@ export class TreeView extends React.Component<TreeViewProps> { fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? 'bold' : undefined, textDecoration: Doc.GetT(this.doc, 'title', 'string', true) ? 'underline' : undefined, outline: this.doc === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined, - pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined, + pointerEvents: !this.props.isContentActive() ? 'none' : undefined, }}> {view} </div> @@ -1132,7 +1159,7 @@ export class TreeView extends React.Component<TreeViewProps> { add: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean, remove: undefined | ((doc: Doc | Doc[]) => boolean), move: DragManager.MoveFunction, - dropAction: dropActionType, + dragAction: dropActionType, addDocTab: (doc: Doc, where: OpenWhere) => boolean, styleProvider: undefined | StyleProviderFunc, screenToLocalXf: () => Transform, @@ -1182,7 +1209,7 @@ export class TreeView extends React.Component<TreeViewProps> { TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined; Doc.AddDocToList(newParent, fieldKey, child, addAfter, false); newParent.treeViewOpen = true; - child.embedContainer = treeView.Document; + Doc.SetContainer(child, treeView.Document); } }; const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); @@ -1218,7 +1245,7 @@ export class TreeView extends React.Component<TreeViewProps> { panelHeight={rowHeight} dontRegisterView={dontRegisterView} moveDocument={move} - dropAction={dropAction} + dragAction={dragAction} addDocTab={addDocTab} ScreenToLocalTransform={screenToLocalXf} isContentActive={isContentActive} @@ -1239,6 +1266,6 @@ export class TreeView extends React.Component<TreeViewProps> { ScriptingGlobals.add(function TreeView_addNewFolder() { TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined }; - const opts = { title: 'Untitled folder', _stayInCollection: true, isFolder: true }; + const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }; return Doc.AddDocToList(Doc.MyFilesystem, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id)); }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 11151e74e..ba31916a7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -418,15 +418,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @undoBatch internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) { if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) { - // dragged document is a child of this collection - if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.embedContainer !== this.props.Document) { - // if the source doc view's embedContainer isn't this same freeformcollectionlinkDragData.dragDocument.embedContainer === this.props.Document + let added = false; + // do nothing if link is dropped into any freeform view parent of dragged document + if (!linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.rootDoc) { const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' }); - this.props.addDocument?.(source); + added = this.props.addDocument?.(source) ? true : false; de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed } - e.stopPropagation(); // do nothing if link is dropped into any freeform view parent of dragged document - return true; + e.stopPropagation(); + !added && e.preventDefault(); + return added; } return false; } @@ -1052,7 +1053,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (this.layoutDoc._Transform || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; e.stopPropagation(); const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight); - const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling; + const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling + 1e-4; switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) { case freeformScrollMode.Pan: // if ctrl is selected then zoom @@ -1240,7 +1241,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; }; - isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + isContentActive = () => this.props.isContentActive(); @undoBatch @action @@ -1267,7 +1268,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); const pointerEvents = DocumentDecorations.Instance.Interacting ? 'none' - : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); + : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.()); return pointerEvents; }; getChildDocView(entry: PoolData) { @@ -1297,8 +1298,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection childFilters={this.childDocFilters} childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive} - isContentActive={this.props.childContentsActive ?? emptyFunction} + isDocumentActive={this.props.childDocumentsActive?.() || this.rootDoc._isGroup ? this.props.isDocumentActive : this.isContentActive} + isContentActive={this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction} focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus} addDocTab={this.addDocTab} addDocument={this.props.addDocument} @@ -1308,10 +1309,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} docViewPath={this.props.docViewPath} styleProvider={this.getClusterColor} - canEmbedOnDrag={this.props.childCanEmbedOnDrag} + dragAction={(this.rootDoc.childDragAction ?? this.props.childDragAction) as dropActionType} dataProvider={this.childDataProvider} sizeProvider={this.childSizeProvider} - dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} bringToFront={this.bringToFront} layout_showTitle={this.props.childlayout_showTitle} dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView} @@ -1535,7 +1535,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type) - const anchor = Docs.Create.CollectionConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presTransition: 500, annotationOn: this.rootDoc }); + const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presTransition: 500, annotationOn: this.rootDoc }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, type_collection: true, filters: true } }, this.rootDoc); if (addAsAnnotation) { @@ -1987,7 +1987,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onDragOver={e => e.preventDefault()} onContextMenu={this.onContextMenu} style={{ - pointerEvents: SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement()) ? 'all' : (this.props.pointerEvents?.() as any), + pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : (this.props.pointerEvents?.() as any), textAlign: this.isAnnotationOverlay ? 'initial' : undefined, transform: `scale(${this.nativeDimScaling || 1})`, width: `${100 / (this.nativeDimScaling || 1)}%`, @@ -2256,22 +2256,20 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) { const browseTransitionTime = 500; SelectionManager.DeselectAll(); - if ( - dv.props.focus(dv.props.Document, { - willZoomCentered: true, - zoomScale: 0.8, - zoomTime: browseTransitionTime, - }) === undefined - ) { - const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; - let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview - ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime); - Doc.linkFollowHighlight(dv?.props.Document, false); - } else { - DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }); - } + DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => { + if (!focused) { + const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; + let containers = dv.props.docViewPath(); + let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + for (var cont of containers) { + parFfview = parFfview ?? cont.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + } + while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview + ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime); + Doc.linkFollowHighlight(dv?.props.Document, false); + } + }); } ScriptingGlobals.add(CollectionBrowseClick); ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index c9168d40a..0f51fe6ff 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -4,6 +4,10 @@ import { Tooltip } from '@material-ui/core'; import { observer } from 'mobx-react'; import { unimplementedFunction } from '../../../../Utils'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; +import { IconButton } from 'browndash-components'; +import { StrCast } from '../../../../fields/Types'; +import { Doc } from '../../../../fields/Doc'; +import { computed } from 'mobx'; @observer export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -22,39 +26,44 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { MarqueeOptionsMenu.Instance = this; } + @computed get userColor() { + return StrCast(Doc.UserDoc().userColor) + } + render() { - const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: 'auto', width: 19, transform: 'translate(-2px, -2px)' }} />; + const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ width: 19 }} />; const buttons = ( <> - <Tooltip key="collect" title={<div className="dash-tooltip">Create a Collection</div>} placement="bottom"> - <button className="antimodeMenu-button" onPointerDown={this.createCollection}> - <FontAwesomeIcon icon="object-group" size="lg" /> - </button> - </Tooltip> - , - <Tooltip key="group" title={<div className="dash-tooltip">Create a Grouping</div>} placement="bottom"> - <button className="antimodeMenu-button" onPointerDown={e => this.createCollection(e, true)}> - <FontAwesomeIcon icon="layer-group" size="lg" /> - </button> - </Tooltip> - , - <Tooltip key="summarize" title={<div className="dash-tooltip">Summarize Documents</div>} placement="bottom"> - <button className="antimodeMenu-button" onPointerDown={this.summarize}> - <FontAwesomeIcon icon="compress-arrows-alt" size="lg" /> - </button> - </Tooltip> - , - <Tooltip key="delete" title={<div className="dash-tooltip">Delete Documents</div>} placement="bottom"> - <button className="antimodeMenu-button" onPointerDown={this.delete}> - <FontAwesomeIcon icon="trash-alt" size="lg" /> - </button> - </Tooltip> - , - <Tooltip key="pinWithView" title={<div className="dash-tooltip">Pin selected region to trail</div>} placement="bottom"> - <button className="antimodeMenu-button" onPointerDown={this.pinWithView}> - {presPinWithViewIcon} - </button> - </Tooltip> + <IconButton + tooltip={"Create a Collection"} + onPointerDown={this.createCollection} + icon={<FontAwesomeIcon icon="object-group"/>} + color={this.userColor} + /> + <IconButton + tooltip={"Create a Grouping"} + onPointerDown={e => this.createCollection(e, true)} + icon={<FontAwesomeIcon icon="layer-group"/>} + color={this.userColor} + /> + <IconButton + tooltip={"Summarize Documents"} + onPointerDown={this.summarize} + icon={<FontAwesomeIcon icon="compress-arrows-alt"/>} + color={this.userColor} + /> + <IconButton + tooltip={"Delete Documents"} + onPointerDown={this.delete} + icon={<FontAwesomeIcon icon="trash-alt"/>} + color={this.userColor} + /> + <IconButton + tooltip={"Pin selected region"} + onPointerDown={this.pinWithView} + icon={presPinWithViewIcon} + color={this.userColor} + /> </> ); return this.getElement(buttons); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 77cb7f88c..e2718b52d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -9,7 +9,7 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; import { Cast, DocCast, FieldValue, NumCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { GetEffectiveAcl } from '../../../../fields/util'; +import { distributeAcls, GetEffectiveAcl, SharingPermissions } from '../../../../fields/util'; import { intersectRect, returnFalse, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents'; @@ -385,12 +385,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque newCollection._width = this.Bounds.width; newCollection._height = this.Bounds.height; newCollection._isGroup = makeGroup; + newCollection._dragWhenActive = makeGroup; newCollection.forceActive = makeGroup; - newCollection.enableDragWhenActive = makeGroup; newCollection.x = this.Bounds.left; newCollection.y = this.Bounds.top; newCollection.layout_fitWidth = true; - selected.forEach(d => (d.embedContainer = newCollection)); + selected.forEach(d => Doc.SetContainer(d, newCollection)); this.hideMarquee(); return newCollection; }); diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index e8ae88ae5..cd8b7a0cc 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -368,7 +368,7 @@ export class CollectionGridView extends CollectionSubView() { <div className="collectionGridView-contents" ref={this.createDashEventsTarget} - style={{ pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined }} + style={{ pointerEvents: !this.props.isContentActive() ? 'none' : undefined }} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onDrop={this.onExternalDrop}> diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss index 3e3709827..6b3318bf3 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss @@ -17,12 +17,7 @@ .collectionLinearView-menuOpener { user-select: none; } - - &.true { - border-left: $standard-border; - background-color: $medium-blue-alt; - } - + > input:not(:checked) ~ &.true { background-color: transparent; } diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 65578f214..56b8366d0 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, Opt } from '../../../../fields/Doc'; @@ -20,6 +20,8 @@ import { UndoStack } from '../../UndoStack'; import { CollectionStackedTimeline } from '../CollectionStackedTimeline'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionLinearView.scss'; +import { Button, Toggle, ToggleType, Type } from 'browndash-components'; +import { Colors } from '../../global/globalEnums'; /** * CollectionLinearView is the class for rendering the horizontal collection @@ -46,38 +48,14 @@ export class CollectionLinearView extends CollectionSubView() { componentDidMount() { this._widthDisposer = reaction( - () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsExpanded ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[Width]() || this.dimension()) + tot + 4, 0) : 0), + () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[Width]() || this.dimension()) + tot + 4, 0) : 0), width => this.childDocs.length && (this.layoutDoc._width = width), { fireImmediately: true } ); - - this._selectedDisposer = reaction( - () => NumCast(this.layoutDoc.selectedIndex), - i => - runInAction(() => { - this._selectedIndex = i; - let selected: any = undefined; - this.childLayoutPairs.map(async (pair, ind) => { - const isSelected = this._selectedIndex === ind; - if (isSelected) { - selected = pair; - } else { - ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log); - } - }); - if (selected && selected.layout) { - ScriptCast(DocCast(selected.layout.proto)?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log); - } - }), - { fireImmediately: true } - ); } protected createDashEventsTarget = (ele: HTMLDivElement | null) => { - //used for stacking and masonry view - this._dropDisposer && this._dropDisposer(); - if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); - } + this._dropDisposer?.(); + if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); }; dimension = () => NumCast(this.rootDoc._height); @@ -89,12 +67,8 @@ export class CollectionLinearView extends CollectionSubView() { @action exitLongLinks = () => { - if (DocumentLinksButton.StartLink) { - if (DocumentLinksButton.StartLink.Document) { - action((e: React.PointerEvent<HTMLDivElement>) => { - Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc); - }); - } + if (DocumentLinksButton.StartLink?.Document) { + action((e: React.PointerEvent<HTMLDivElement>) => Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc)); } DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; @@ -201,7 +175,7 @@ export class CollectionLinearView extends CollectionSubView() { moveDocument={this.props.moveDocument} addDocTab={this.props.addDocTab} pinToPres={emptyFunction} - dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} + dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType} rootSelected={this.props.isSelected} removeDocument={this.props.removeDocument} ScreenToLocalTransform={docXf} @@ -226,54 +200,48 @@ export class CollectionLinearView extends CollectionSubView() { render() { const flexDir = StrCast(this.Document.flexDirection); // Specify direction of linear view content const flexGap = NumCast(this.Document.flexGap); // Specify the gap between linear view content - const isExpanded = BoolCast(this.layoutDoc.linearView_IsExpanded); + const isExpanded = BoolCast(this.layoutDoc.linearView_IsOpen); const menuOpener = ( - <label - className={`collectionlinearView-label${isExpanded ? '-expanded' : ''}`} - htmlFor={this.Document[Id] + '-input'} - style={{ boxShadow: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BoxShadow) }} - onPointerDown={StopEvent}> - <div className="collectionLinearView-menuOpener">{Cast(this.props.Document.icon, 'string', null) ?? <FontAwesomeIcon icon={isExpanded ? 'minus' : 'plus'} />}</div> - </label> + <Toggle + text={Cast(this.props.Document.icon, 'string', null)} + icon={Cast(this.props.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon icon={isExpanded ? 'minus' : 'plus'} />} + type={Type.TERT} + color={StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE)} + onPointerDown={e => e.stopPropagation()} + toggleType={ToggleType.BUTTON} + toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)} + onClick={() => { + this.layoutDoc.linearView_IsOpen = !isExpanded; + }} + tooltip={isExpanded ? 'Close' : 'Open'} + fillWidth={true} + align={'center'} + /> ); return ( - <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsExpanded ? undefined : 'transparent' }}> - <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension() }}> - {!this.props.Document.linearView_Expandable ? null : ( - <Tooltip title={<div className="dash-tooltip">{isExpanded ? 'Close' : 'Open'}</div>} placement="top"> - {menuOpener} - </Tooltip> - )} - <input - id={this.Document[Id] + '-input'} - type="checkbox" - checked={isExpanded} - ref={this.addMenuToggle} - onChange={action(e => { - ScriptCast(this.Document.onClick)?.script.run({ - this: this.layoutDoc, - self: this.rootDoc, - _readOnly_: false, - scriptContext: this.props.scriptContext, - documentView: this.props.DocumentView?.(), - }); - this.layoutDoc.linearView_IsExpanded = this.addMenuToggle.current!.checked; - })} - /> - - {!this.layoutDoc.linearView_IsExpanded ? null : ( - <div - className="collectionLinearView-content" - style={{ - height: this.dimension(), - flexDirection: flexDir as any, - gap: flexGap, - }}> - {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} - </div> - )} + <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsOpen ? undefined : 'transparent' }}> + <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}> + {this.props.Document.linearView_Dropdown ? + <div>Hello World!</div> + : + <> + {!this.props.Document.linearView_Expandable ? null : menuOpener} + {!this.layoutDoc.linearView_IsOpen ? null : ( + <div + className="collectionLinearView-content" + style={{ + height: this.dimension(), + flexDirection: flexDir as any, + gap: flexGap, + }}> + {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} + </div> + )} + </> + } + </div> </div> ); diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss index 821c8d804..f87a06033 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss @@ -8,6 +8,11 @@ display: flex; flex-direction: column; width: 100%; + align-items: center; + + .contentFittingDocumentView { + width: unset; + } .label-wrapper { display: flex; @@ -15,7 +20,6 @@ justify-content: center; height: 20px; } - } .multiColumnResizer { @@ -30,5 +34,4 @@ transition: 0.5s background-color ease; } } - -}
\ No newline at end of file +} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 34fa0343d..10532b9d9 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -5,7 +5,6 @@ import { Doc, DocListCast } from '../../../../fields/Doc'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { returnFalse } from '../../../../Utils'; import { DragManager, dropActionType } from '../../../util/DragManager'; -import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { DocumentView } from '../../nodes/DocumentView'; @@ -194,11 +193,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { let dropInd = -1; if (de.complete.docDragData && this._mainCont) { let curInd = -1; - de.complete.docDragData?.droppedDocuments.forEach( - action((d: Doc) => { - curInd = this.childDocs.indexOf(d); - }) - ); + de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d))); Array.from(this._mainCont.children).forEach((child, index) => { const brect = child.getBoundingClientRect(); if (brect.x < de.x && brect.x + brect.width > de.x) { @@ -225,6 +220,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { } }) ); + return true; } } return false; @@ -234,8 +230,15 @@ export class CollectionMulticolumnView extends CollectionSubView() { onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); - isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); - getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { + isChildContentActive = () => { + const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive; + return this.props.isContentActive?.() === false || childDocsActive === false + ? false // + : this.props.isDocumentActive?.() && childDocsActive + ? true + : undefined; + }; + getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number, shouldNotScale: () => boolean) => { return ( <DocumentView Document={layout} @@ -247,8 +250,9 @@ export class CollectionMulticolumnView extends CollectionSubView() { renderDepth={this.props.renderDepth + 1} PanelWidth={width} PanelHeight={height} + shouldNotScale={shouldNotScale} rootSelected={this.rootSelected} - dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} + dragAction={(this.props.Document.childDragAction ?? this.props.childDragAction) as dropActionType} onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} suppressSetHeight={true} @@ -284,15 +288,19 @@ export class CollectionMulticolumnView extends CollectionSubView() { const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; + const aspect = Doc.NativeAspect(layout, undefined, true); + const width = () => this.lookupPixels(layout); + const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); + const docwidth = () => (layout._layout_forceReflow ? width() : Math.min(height() * aspect, width())); + const docheight = () => Math.min(docwidth() / aspect, height()); const dxf = () => this.lookupIndividualTransform(layout) - .translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)) + .translate(-NumCast(Document._xMargin) - (width() - docwidth()) / 2, -NumCast(Document._yMargin)) .scale(this.props.NativeDimScaling?.() || 1); - const width = () => this.lookupPixels(layout); - const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); + const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox); collector.push( - <div className={'document-wrapper'} key={'wrapper' + i} style={{ width: width() }}> - {this.getDisplayDoc(layout, dxf, width, height)} + <div className="document-wrapper" key={'wrapper' + i} style={{ width: width() }}> + {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)} <WidthLabel layout={layout} collectionDoc={Document} /> </div>, <ResizeBar diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss index 79fb195e8..ec7200a03 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss @@ -9,6 +9,12 @@ display: flex; flex-direction: row; height: 100%; + align-items: center; + margin: auto; + + .contentFittingDocumentView { + height: unset; + } .label-wrapper { display: flex; @@ -16,7 +22,6 @@ justify-content: center; height: 20px; } - } .multiRowResizer { @@ -31,5 +36,4 @@ transition: 0.5s background-color ease; } } - -}
\ No newline at end of file +} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index bab77ed48..04cfc5456 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -5,7 +5,6 @@ import { Doc, DocListCast } from '../../../../fields/Doc'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { returnFalse } from '../../../../Utils'; import { DragManager, dropActionType } from '../../../util/DragManager'; -import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { DocumentView } from '../../nodes/DocumentView'; @@ -194,11 +193,7 @@ export class CollectionMultirowView extends CollectionSubView() { let dropInd = -1; if (de.complete.docDragData && this._mainCont) { let curInd = -1; - de.complete.docDragData?.droppedDocuments.forEach( - action((d: Doc) => { - curInd = this.childDocs.indexOf(d); - }) - ); + de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d))); Array.from(this._mainCont.children).forEach((child, index) => { const brect = child.getBoundingClientRect(); if (brect.y < de.y && brect.y + brect.height > de.y) { @@ -225,6 +220,7 @@ export class CollectionMultirowView extends CollectionSubView() { } }) ); + return true; } } return false; @@ -234,8 +230,15 @@ export class CollectionMultirowView extends CollectionSubView() { onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); - isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); - getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { + isChildContentActive = () => { + const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive; + return this.props.isContentActive?.() === false || childDocsActive === false + ? false // + : this.props.isDocumentActive?.() && childDocsActive + ? true + : undefined; + }; + getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number, shouldNotScale: () => boolean) => { return ( <DocumentView Document={layout} @@ -247,8 +250,9 @@ export class CollectionMultirowView extends CollectionSubView() { renderDepth={this.props.renderDepth + 1} PanelWidth={width} PanelHeight={height} + shouldNotScale={shouldNotScale} rootSelected={this.rootSelected} - dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} + dropAction={StrCast(this.rootDoc.childDragAction) as dropActionType} onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} ScreenToLocalTransform={dxf} @@ -257,6 +261,7 @@ export class CollectionMultirowView extends CollectionSubView() { hideResizeHandles={this.props.childHideResizeHandles?.()} hideDecorationTitle={this.props.childHideDecorationTitle?.()} fitContentsToBox={this.props.fitContentsToBox} + dragAction={this.props.childDragAction} focus={this.props.focus} childFilters={this.childDocFilters} childFiltersByRanges={this.childDocRangeFilters} @@ -283,15 +288,19 @@ export class CollectionMultirowView extends CollectionSubView() { const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; + const aspect = Doc.NativeAspect(layout, undefined, true); + const height = () => this.lookupPixels(layout); + const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); + const docheight = () => Math.min(width() / aspect, height()); + const docwidth = () => (layout._layout_forceReflow ? width() : Math.min(width(), docheight() * aspect)); const dxf = () => this.lookupIndividualTransform(layout) - .translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)) + .translate(-NumCast(Document._xMargin) - (width() - docwidth()) / 2, -NumCast(Document._yMargin) - (height() - docheight()) / 2) .scale(this.props.NativeDimScaling?.() || 1); - const height = () => this.lookupPixels(layout); - const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); + const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox); collector.push( - <div className={'document-wrapper'} style={{ height: height() }} key={'wrapper' + i}> - {this.getDisplayDoc(layout, dxf, width, height)} + <div className="document-wrapper" style={{ height: height() }} key={'wrapper' + i}> + {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)} <HeightLabel layout={layout} collectionDoc={Document} /> </div>, <ResizeBar diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 15424de98..ee5bf82ed 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -434,7 +434,6 @@ export class CollectionSchemaView extends CollectionSubView() { @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.columnDragData) { - e.stopPropagation(); const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0]; const index = this.findDropIndex(mouseX); this.moveColumn(de.complete.columnDragData.colIndex, index ?? de.complete.columnDragData.colIndex); @@ -448,6 +447,7 @@ export class CollectionSchemaView extends CollectionSubView() { }); }); + e.stopPropagation(); return true; } const draggedDocs = de.complete.docDragData?.draggedDocuments; @@ -462,7 +462,6 @@ export class CollectionSchemaView extends CollectionSubView() { if (draggedView) DocumentManager.Instance.RemoveView(draggedView); DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true)); }); - e.stopPropagation(); return true; } return false; @@ -952,7 +951,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP styleProvider={DefaultStyleProvider} waitForDoubleClickToClick={returnNever} defaultDoubleClick={returnIgnore} - enableDragWhenActive={true} + dragAction="move" onClickScriptDisable="always" focus={this.props.schema.focusDocument} childFilters={this.props.schema.childDocFilters} @@ -970,7 +969,6 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP hideLinkAnchors={true} layout_fitWidth={returnTrue} scriptContext={this} - canEmbedOnDrag={true} /> </div> ); diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 4f3503751..e8e606030 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -53,13 +53,13 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() { }; onPointerEnter = (e: any) => { - if (!SnappingManager.GetIsDragging()) return; - document.removeEventListener('pointermove', this.onPointerMove); - document.addEventListener('pointermove', this.onPointerMove); + if (SnappingManager.GetIsDragging() && this.props.isContentActive()) { + document.removeEventListener('pointermove', this.onPointerMove); + document.addEventListener('pointermove', this.onPointerMove); + } }; onPointerMove = (e: any) => { - if (!SnappingManager.GetIsDragging()) return; const dragIsRow = DragManager.docsBeingDragged.some(doc => doc.embedContainer === this.schemaDoc); // this.schemaView?._selectedDocs.has(doc) ?? false; if (this._ref && dragIsRow) { diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 97264508c..1c9c0de53 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -76,7 +76,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { isSelected: returnFalse, setHeight: returnFalse, select: emptyFunction, - dropAction: 'embed', + dragAction: 'move', bringToFront: emptyFunction, renderDepth: 1, isContentActive: returnFalse, @@ -272,7 +272,7 @@ export class SchemaRTFCell extends React.Component<SchemaTableCellProps> { fieldProps.isContentActive = this.selectedFunc; return ( <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}> - {this.selected ? <FormattedTextBox allowScroll={true} {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} + {this.selected ? <FormattedTextBox allowScroll={true} {...fieldProps} DataDoc={this.props.Document} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} </div> ); } |
