diff options
| author | bobzel <zzzman@gmail.com> | 2022-06-03 08:39:14 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2022-06-03 08:39:14 -0400 |
| commit | 3e75896a0c6e59036a6b9467521f4fe68fdda1a0 (patch) | |
| tree | 758cfb37a9a17d4b276f7bcc908a56185b718978 /src/client/views/collections | |
| parent | ca26b43095622d07ae81fc08d4037be38d9a8b28 (diff) | |
| parent | 8799738abd11a878579814e64163e0f8a95b5116 (diff) | |
Merge branch 'master' into presentation_upgrade
Diffstat (limited to 'src/client/views/collections')
22 files changed, 459 insertions, 335 deletions
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index cec71471b..7e1299a2e 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -81,6 +81,8 @@ export class CollectionDockingView extends CollectionSubView() { tabItemDropped = () => DragManager.CompleteWindowDrag?.(false); tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => { + const dashDoc = proxy?._contentItem?.tab?.DashDoc as Doc; + dashDoc && (DragManager.DocDragData = new DragManager.DocumentDragData([proxy._contentItem.tab.DashDoc])); DragManager.CompleteWindowDrag = (aborted: boolean) => { if (aborted) { proxy._dragListener.AbortDrag(); @@ -184,18 +186,6 @@ export class CollectionDockingView extends CollectionSubView() { } const instance = CollectionDockingView.Instance; if (!instance) return false; - else { - const docList = DocListCast(instance.props.Document[DataSym]["data-all"]); - // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases - !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data-all", document); - // adds an alias of the doc to the data-all field of the layoutdocs of the aliases - DocListCast(instance.props.Document[DataSym].aliases).forEach(alias => { - const aliasDocList = DocListCast(alias["data-all"]); - // if aliasDocList contains the alias, don't do anything - // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias - !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data-all", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document); - }); - } const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName); if (!pullSide && stack) { @@ -404,7 +394,10 @@ export class CollectionDockingView extends CollectionSubView() { const origtabdocs = DocListCast(origtab.data); const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true, undefined, true) : Doc.MakeAlias(origtab); const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeAlias(origtabdoc)); - newtabdocs.length && (Doc.GetProto(newtab).data = new List<Doc>(newtabdocs)); + if (newtabdocs.length) { + Doc.GetProto(newtab).data = new List<Doc>(newtabdocs); + newtabdocs.forEach(ntab => ntab.context = newtab); + } json = json.replace(origtab[Id], newtab[Id]); return newtab; }); @@ -418,29 +411,10 @@ export class CollectionDockingView extends CollectionSubView() { const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")); const docs = !docids ? [] : docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc); - const changesMade = this.props.Document.dockingConfig !== json; + const changesMade = this.props.Document.dockcingConfig !== json; if (changesMade && !this._flush) { this.props.Document.dockingConfig = json; - setTimeout(async () => { - const sublists = await DocListCastAsync(this.props.Document[this.props.fieldKey]); - const tabs = sublists && Cast(sublists[0], Doc, null); - // const other = sublists && Cast(sublists[1], Doc, null); - const tabdocs = await DocListCastAsync(tabs?.data); - // const otherdocs = await DocListCastAsync(other?.data); - if (tabs) { - tabs.data = new List<Doc>(docs); - // DocListCast(tabs.aliases).forEach(tab => tab !== tabs && (tab.data = new List<Doc>(docs))); - } - // const otherSet = new Set<Doc>(); - // otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc)); - // tabdocs?.filter(doc => !docs.includes(doc) && doc.type !== DocumentType.KVP).forEach(doc => otherSet.add(doc)); - // const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP); - // this.props.Document[DataSym][this.props.fieldKey + "-all"] = new List<Doc>([...docs, ...vals]); - // if (other) { - // other.data = new List<Doc>(vals); - // // DocListCast(other.aliases).forEach(tab => tab !== other && (tab.data = new List<Doc>(vals))); - // } - }, 0); + this.props.Document.data = new List<Doc>(docs); } return changesMade; } @@ -474,7 +448,6 @@ export class CollectionDockingView extends CollectionSubView() { //if (confirm('really close this?')) { if (!stack.parent.parent.isRoot || stack.parent.contentItems.length > 1) { stack.remove(); - stack.contentItems.forEach((contentItem: any) => Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", contentItem.tab.DashDoc, undefined, true, true)); } else { alert('cant delete the last stack'); } diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 364a2440e..96029a8e4 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -84,11 +84,11 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps>{ } @action - toggleProperties = () => { - if (CurrentUserUtils.propertiesWidth > 0) { - CurrentUserUtils.propertiesWidth = 0; + toggleTopBar = () => { + if (CurrentUserUtils.headerBarHeight > 0) { + CurrentUserUtils.headerBarHeight = 0; } else { - CurrentUserUtils.propertiesWidth = 250; + CurrentUserUtils.headerBarHeight = 60; } } @@ -108,7 +108,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps>{ dropAction={"alias"} setHeight={returnFalse} styleProvider={DefaultStyleProvider} - layerProvider={undefined} rootSelected={returnTrue} bringToFront={emptyFunction} select={emptyFunction} @@ -138,14 +137,13 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps>{ render() { - const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left"; - const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel"; + const propIcon = CurrentUserUtils.headerBarHeight > 0 ? "angle-double-up" : "angle-double-down"; + const propTitle = CurrentUserUtils.headerBarHeight > 0 ? "Close Header Bar" : "Open Header Bar"; - const prop = <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="properties" placement="bottom"> + const prop = <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="topar" placement="bottom"> <div className="collectionMenu-hardCodedButton" style={{ backgroundColor: CurrentUserUtils.propertiesWidth > 0 ? Colors.MEDIUM_BLUE : undefined }} - key="properties" - onPointerDown={this.toggleProperties}> + onPointerDown={this.toggleTopBar}> <FontAwesomeIcon icon={propIcon} size="lg" /> </div> </Tooltip>; @@ -468,7 +466,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu @undoBatch @action startRecording = () => { - const doc = Docs.Create.ScreenshotDocument("screen recording", { _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" }); + const doc = Docs.Create.ScreenshotDocument({ title: "screen recording", _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" }); //Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); CollectionDockingView.AddSplit(doc, "right"); } diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index de307416f..5a1cc4ded 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -22,6 +22,7 @@ import { returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from "../../../Utils"; import { Docs } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; import { LinkManager } from "../../util/LinkManager"; @@ -182,7 +183,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack ) { // if shift pressed scrub 1 second otherwise 1/10th const jump = e.shiftKey ? 1 : 0.1; - e.stopPropagation(); switch (e.key) { case " ": if (!CollectionStackedTimeline.SelectingRegion) { @@ -202,18 +202,22 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._markerEnd = undefined; CollectionStackedTimeline.SelectingRegion = undefined; } + e.stopPropagation(); break; case "Escape": // abandons current trim this._trimStart = this.clipStart; this._trimStart = this.clipEnd; this._trimming = TrimScope.None; + e.stopPropagation(); break; case "ArrowLeft": this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd)); + e.stopPropagation(); break; case "ArrowRight": this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd)); + e.stopPropagation(); break; } } @@ -392,7 +396,7 @@ 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.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false; + 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 @@ -434,6 +438,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack title: ComputedField.MakeFunction( `"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])` ) as any, + _minFontSize: 12, + _maxFontSize: 24, + _singleLine: false, _stayInCollection: true, useLinkSmallAnchor: true, hideLinkButton: true, @@ -785,6 +792,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> // updates marker document title to reflect correct timecodes computeTitle = () => { + if (this.props.mark.type !== DocumentType.LABEL) return undefined; const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart; const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart; return `#${formatTime(start)}-${formatTime(end)}`; @@ -918,7 +926,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> DataDoc={undefined} renderDepth={this.props.renderDepth + 1} LayoutTemplate={undefined} - LayoutTemplateString={LabelBox.LayoutStringWithTitle(LabelBox, "data", this.computeTitle())} + LayoutTemplateString={LabelBox.LayoutStringWithTitle("data", this.computeTitle())} isDocumentActive={this.props.isDocumentActive} PanelWidth={width} PanelHeight={height} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 509005b45..dddae4a34 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -28,6 +28,8 @@ import "./CollectionStackingView.scss"; import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; +import { FieldViewProps } from "../nodes/FieldView"; +import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; const _global = (window /* browser */ || global /* node */) as any; @@ -207,6 +209,25 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } return this.props.styleProvider?.(doc, props, property); } + @undoBatch + @action + onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { + const docView = fieldProps.DocumentView?.(); + if (docView && ["Enter"].includes(e.key) && e.ctrlKey) { + e.stopPropagation?.(); + const below = !e.altKey && e.key !== "Tab"; + const layoutKey = StrCast(docView.LayoutFieldKey); + const newDoc = Doc.MakeCopy(docView.rootDoc, true); + const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)]; + newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined; + if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) { + newDoc[layoutKey] = docView.rootDoc[layoutKey]; + } + Doc.GetProto(newDoc).text = undefined; + FormattedTextBox.SelectOnLoad = newDoc[Id]; + return this.addDocument?.(newDoc); + } + } isContentActive = () => this.props.isSelected() || this.props.isContentActive(); getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; @@ -222,10 +243,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection PanelWidth={width} PanelHeight={height} styleProvider={this.styleProvider} - layerProvider={this.props.layerProvider} docViewPath={this.props.docViewPath} fitWidth={this.props.childFitWidth} isContentActive={emptyFunction} + onKey={this.onKeyDown} isDocumentActive={this.isContentActive} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} @@ -483,7 +504,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection if (value && this.columnHeaders) { const schemaHdrField = new SchemaHeaderField(value); this.columnHeaders.push(schemaHdrField); - DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: "schemaHdrField.color" }]); return true; } return false; @@ -543,7 +563,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection renderDepth={this.props.renderDepth} focus={emptyFunction} styleProvider={this.props.styleProvider} - layerProvider={this.props.layerProvider} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 30e0adf24..17fdba764 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -316,28 +316,20 @@ export function CollectionSubView<X>(moreProps?: X) { } }); } else { - const srcWeb = SelectionManager.Docs().lastElement(); - const srcUrl = (srcWeb?.data as WebField).url?.href?.match(/http[s]?:\/\/[^/]*/)?.[0]; + const srcWeb = SelectionManager.Views().lastElement(); + const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0]; const reg = new RegExp(Utils.prepend(""), "g"); const modHtml = srcUrl ? html.replace(reg, srcUrl) : html; - const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 }); + const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, "$1")).filter(t => t)?.[0]; + const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? "from:" + srcUrl : "-web clip-", _width: 300, _height: 300, backgroundColor }); Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc).text = text; addDocument(htmlDoc); if (srcWeb) { const iframe = SelectionManager.Views()[0].ContentDiv?.getElementsByTagName("iframe")?.[0]; const focusNode = (iframe?.contentDocument?.getSelection()?.focusNode as any); if (focusNode) { - const rects = iframe?.contentWindow?.getSelection()?.getRangeAt(0).getClientRects(); - "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect(); - const x = (rects && Array.from(rects).reduce((x: any, r: DOMRect) => x === undefined || r.x < x ? r.x : x, undefined as any)) || 0; - const y = NumCast(srcWeb._scrollTop) + ((rects && Array.from(rects).reduce((y: any, r: DOMRect) => y === undefined || r.y < y ? r.y : y, undefined as any)) || 0); - const r = (rects && Array.from(rects).reduce((x: any, r: DOMRect) => x === undefined || r.x + r.width > x ? r.x + r.width : x, undefined as any)) || 0; - const b = NumCast(srcWeb._scrollTop) + ((rects && Array.from(rects).reduce((y: any, r: DOMRect) => y === undefined || r.y + r.height > y ? r.y + r.height : y, undefined as any)) || 0); - const anchor = Docs.Create.FreeformDocument([], { backgroundColor: "transparent", _width: r - x, _height: b - y, x, y, annotationOn: srcWeb }); - anchor.context = srcWeb; - const key = Doc.LayoutFieldKey(srcWeb); - Doc.AddDocToList(srcWeb, key + "-annotations", anchor); - DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor }); + const anchor = srcWeb?.ComponentView?.getAnchor?.(); + anchor && DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor }); } } } diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index e63ea7151..4f6f45d2f 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -10,6 +10,7 @@ import { ComputedField, ScriptField } from "../../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils"; import { Docs } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from "../../util/DocumentManager"; import { ScriptingGlobals } from "../../util/ScriptingGlobals"; import { ContextMenu } from "../ContextMenu"; @@ -128,6 +129,7 @@ export class CollectionTimeView extends CollectionSubView() { } } + dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF; @computed get contents() { return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : "none" }} onClick={this.contentsDown}> @@ -137,6 +139,7 @@ export class CollectionTimeView extends CollectionSubView() { childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} childFreezeDimensions={true} + dontScaleFilter={this.dontScaleFilter} layoutEngine={this.layoutEngine} /> </div>; } @@ -174,7 +177,7 @@ export class CollectionTimeView extends CollectionSubView() { typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] !== "#" || fieldKey === "#") && (fieldKey === "tags" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey))); Array.from(keySet).map(fieldKey => docItems.push({ description: ":" + fieldKey, event: () => this.layoutDoc._pivotField = fieldKey, icon: "compress-arrows-alt" })); - docItems.push({ description: ":(null)", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" }); + docItems.push({ description: ":default", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" }); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y); ContextMenu.Instance.displayMenu(x, y, ":"); @@ -234,16 +237,17 @@ export class CollectionTimeView extends CollectionSubView() { } ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) { + const pivotField = StrCast(pivotDoc._pivotField) || "author"; let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex); const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField)); pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField); pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField); - pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotDoc._pivotField; + pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotField; pivotDoc._prevFilterIndex = ++prevFilterIndex; - runInAction(() => { - pivotDoc._docFilters = new List(); + pivotDoc._docFilters = new List(); + setTimeout(action(() => { const filterVals = (bounds.payload as string[]); - filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check")); + filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, "check")); const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc); if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) { if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) { @@ -252,11 +256,11 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou } const newFilters = StrListCast(pivotDoc._docFilters); if (newFilters.length && originalFilter.length && - newFilters[newFilters.length - 1] === originalFilter[originalFilter.length - 1]) { + newFilters.lastElement() === originalFilter.lastElement()) { pivotDoc._prevFilterIndex = --prevFilterIndex; pivotDoc["_prevDocFilter" + prevFilterIndex] = undefined; pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = undefined; pivotDoc["_prevPivotFields" + prevFilterIndex] = undefined; } - }); + })); });
\ No newline at end of file diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index a8eee9c19..93523a6cf 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -1,5 +1,6 @@ @import "../global/globalCssVariables"; + .collectionTreeView-container { transform-origin: top left; height: 100%; @@ -31,9 +32,11 @@ width: unset; height: unset; } + &:hover { + cursor: ns-resize; + } } - .no-indent { padding-left: 0; width: max-content; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 5dafb08c0..e809bfbce 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -27,6 +27,7 @@ import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import { TreeView } from "./TreeView"; import React = require("react"); +import { FieldViewProps } from "../nodes/FieldView"; const _global = (window /* browser */ || global /* node */) as any; export type collectionTreeViewProps = { @@ -43,6 +44,12 @@ export type collectionTreeViewProps = { hierarchyIndex?: number[]; }; +export enum TreeViewType { + outline = "outline", + fileSystem = "fileSystem", + default = "default" +} + @observer export class CollectionTreeView extends CollectionSubView<Partial<collectionTreeViewProps>>() { private _treedropDisposer?: DragManager.DragDropDisposer; @@ -58,8 +65,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree @computed get dataDoc() { return this.props.DataDoc || this.doc; } @computed get treeViewtruncateTitleWidth() { return NumCast(this.doc.treeViewTruncateTitleWidth, this.panelWidth()); } @computed get treeChildren() { TraceMobx(); return this.props.childDocuments || this.childDocs; } - @computed get outlineMode() { return this.doc.treeViewType === "outline"; } - @computed get fileSysMode() { return this.doc.treeViewType === "fileSystem"; } + @computed get outlineMode() { return this.doc.treeViewType === TreeViewType.outline; } + @computed get fileSysMode() { return this.doc.treeViewType === TreeViewType.fileSystem; } @computed get dashboardMode() { return this.doc === Doc.UserDoc().myDashboards; } @observable _explainerHeight = 0; // height of the description of the tree view @@ -184,13 +191,20 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree height={"auto"} GetValue={() => StrCast(this.dataDoc.title)} SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { - if (enter && this.props.Document.treeViewType === "outline") this.makeTextCollection(this.treeChildren); + if (enter && this.props.Document.treeViewType === TreeViewType.outline) this.makeTextCollection(this.treeChildren); this.dataDoc.title = value; return true; })} />; } + onKey = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { + if (this.outlineMode && e.key === "Enter") { + e.stopPropagation(); + this.makeTextCollection(this.treeChildren); + return true; + } + } get documentTitle() { return <FormattedTextBox {...this.props} @@ -203,6 +217,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree PanelWidth={this.documentTitleWidth} PanelHeight={this.documentTitleHeight} scaling={returnOne} + onKey={this.onKey} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} @@ -265,13 +280,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree return this.dataDoc === null ? (null) : <div className="collectionTreeView-titleBar" key={this.doc[Id]} style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}} - ref={r => this._titleRef = r} - onKeyDown={e => { - if (this.outlineMode) { - e.stopPropagation(); - e.key === "Enter" && this.makeTextCollection(this.treeChildren); - } - }}> + ref={r => this._titleRef = r}> {this.outlineMode ? this.documentTitle : this.editableTitle} </div>; } @@ -304,7 +313,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree renderDepth={this.props.renderDepth + 1} focus={emptyFunction} styleProvider={this.props.styleProvider} - layerProvider={this.props.layerProvider} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 07bd3957c..0de61c9b2 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -6,7 +6,7 @@ import { Doc, DocListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { ObjectField } from '../../../fields/ObjectField'; import { ScriptField } from '../../../fields/ScriptField'; -import { Cast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { returnEmptyString } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; @@ -241,6 +241,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab bodyPanelWidth = () => this.props.PanelWidth(); + 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); @computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); } @@ -262,10 +264,12 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab ScreenToLocalTransform: this.screenToLocalTransform, childLayoutTemplate: this.childLayoutTemplate, childLayoutString: this.childLayoutString, + childHideResizeHandles: this.childHideResizeHandles, + childHideDecorationTitle: this.childHideDecorationTitle, CollectionView: this, }; return (<div className={"collectionView"} onContextMenu={this.onContextMenu} - style={{ pointerEvents: this.props.layerProvider?.(this.rootDoc) === false ? "none" : undefined }}> + style={{ pointerEvents: this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? "none" : undefined }}> {this.showIsTagged()} {this.renderSubView(this.collectionViewType, props)} </div>); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 07cc543d8..8d84e9a56 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -9,6 +9,7 @@ import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import { DataSym, Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; import { FieldId } from "../../../fields/RefField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils"; @@ -24,16 +25,16 @@ import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { Colors, Shadows } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; +import { MainView } from '../MainView'; import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView"; import { PinProps, PresBox, PresMovement } from '../nodes/trails'; -import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider'; +import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; import { CollectionDockingView } from './CollectionDockingView'; import { CollectionDockingViewMenu } from './CollectionDockingViewMenu'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionView, CollectionViewType } from './CollectionView'; import "./TabDocView.scss"; import React = require("react"); -import { List } from '../../../fields/List'; const _global = (window /* browser */ || global /* node */) as any; interface TabDocViewProps { @@ -83,6 +84,9 @@ export class TabDocView extends React.Component<TabDocViewProps> { titleEle.size = StrCast(doc.title).length + 3; titleEle.value = doc.title; + titleEle.onkeydown = (e: KeyboardEvent) => { + e.stopPropagation(); + }; titleEle.onchange = undoBatch(action((e: any) => { titleEle.size = e.currentTarget.value.length + 3; Doc.GetProto(doc).title = e.currentTarget.value; @@ -94,26 +98,13 @@ export class TabDocView extends React.Component<TabDocViewProps> { if (tab.element[0].children[1].children.length === 1) { - const toggle = document.createElement("div"); - toggle.style.width = "10px"; - toggle.style.height = "calc(100% - 2px)"; - toggle.style.left = "-2px"; - toggle.style.bottom = "1px"; - toggle.style.borderTopRightRadius = "7px"; - toggle.style.position = "relative"; - toggle.style.display = "inline-block"; - toggle.style.background = "transparent"; - toggle.onclick = (e: MouseEvent) => { - if (tab.contentItem === tab.header.parent.getActiveContentItem()) { - tab.DashDoc.activeLayer = tab.DashDoc.activeLayer ? undefined : StyleLayers.Background; - } - }; iconWrap.className = "lm_iconWrap"; iconWrap.id = "lm_iconWrap"; closeWrap.className = "lm_iconWrap"; closeWrap.id = "lm_closeWrap"; closeWrap.onclick = (e: MouseEvent) => { tab.header.parent.contentItem.remove(); + Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", tab.DashDoc); Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true); }; const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />; @@ -189,7 +180,6 @@ export class TabDocView extends React.Component<TabDocViewProps> { tab.closeElement.off('click') //unbind the current click handler .click(function () { Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); - Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true); SelectionManager.DeselectAll(); UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab"); }); @@ -221,7 +211,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { pinDoc.treeViewFieldKey = "data"; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field pinDoc.treeViewExpandedView = "data";// in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view pinDoc.treeViewGrowsHorizontally = true;// the document expands horizontally when displayed as a tree view header - pinDoc.treeViewHideHeader = true; // this will force the document to render itself as the tree view header + pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header const presArray: Doc[] = PresBox.Instance?.sortArray(); const size: number = PresBox.Instance?._selectedArray.size; const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; @@ -246,9 +236,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; const dview = CollectionDockingView.Instance.props.Document; const fieldKey = CollectionDockingView.Instance.props.fieldKey; - const sublists = DocListCast(dview[fieldKey]); - const tabs = Cast(sublists[0], Doc, null); - const tabdocs = await DocListCastAsync(tabs?.data); + const tabdocs = await DocListCastAsync(dview[fieldKey]); runInAction(() => { if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) { tabdocs?.push(curPres); // bcz: Argh! this is annoying. if multiple documents are pinned, this will get called multiple times before the presentation view is drawn. Thus it won't be in the tabdocs list and it will get created multple times. so need to explicilty add the presbox to the list of open tabs @@ -256,7 +244,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { } PresBox.Instance?._selectedArray.clear(); pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array - DocumentManager.Instance.jumpToDocument(doc, false, undefined); + DocumentManager.Instance.jumpToDocument(doc, false, undefined, []); batch.end(); }); } @@ -350,7 +338,9 @@ export class TabDocView extends React.Component<TabDocViewProps> { } } active = () => this._isActive; + @observable _forceInvalidateScreenToLocal = 0; ScreenToLocalTransform = () => { + this._forceInvalidateScreenToLocal; const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement); return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY); } @@ -361,7 +351,6 @@ export class TabDocView extends React.Component<TabDocViewProps> { disableMinimap = () => !this._document || (this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._viewType !== CollectionViewType.Freeform); hideMinimap = () => this.disableMinimap() || BoolCast(this._document?.hideMinimap); - @computed get layerProvider() { return this._document && DefaultLayerProvider(this._document); } @computed get docView() { return !this._activated || !this._document || this._document._viewType === CollectionViewType.Docking ? (null) : <><DocumentView key={this._document[Id]} ref={action((r: DocumentView) => this._view = r)} @@ -370,10 +359,10 @@ export class TabDocView extends React.Component<TabDocViewProps> { DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} + onBrowseClick={MainView.Instance.exploreMode} isContentActive={returnTrue} PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight} - layerProvider={this.layerProvider} styleProvider={DefaultStyleProvider} docFilters={CollectionDockingView.Instance.childDocFilters} docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters} @@ -422,6 +411,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { if (this._mainCont = ref) { (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document); DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document))); + new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref); } }} > {this.docView} @@ -447,9 +437,20 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { default: return DefaultStyleProvider(doc, props, property); case StyleProp.PointerEvents: return "none"; case StyleProp.DocContents: - const background = doc.type === DocumentType.PDF ? "red" : doc.type === DocumentType.IMG ? "blue" : doc.type === DocumentType.RTF ? "orange" : - doc.type === DocumentType.VID ? "purple" : doc.type === DocumentType.WEB ? "yellow" : doc.type === DocumentType.MAP ? "blue" : "gray"; - return doc.type === DocumentType.COL ? + const background = ((type: DocumentType) => { + switch (type) { + case DocumentType.PDF: return "pink"; + case DocumentType.AUDIO: return "lightgreen"; + case DocumentType.WEB: return "brown"; + case DocumentType.IMG: return "blue"; + case DocumentType.MAP: return "orange"; + case DocumentType.VID: return "purple"; + case DocumentType.RTF: return "yellow"; + case DocumentType.COL: return undefined; + default: return "gray"; + } + })(doc.type as DocumentType); + return !background ? undefined : <div style={{ width: doc[WidthSym](), height: doc[HeightSym](), position: "absolute", display: "block", background }} />; } @@ -513,7 +514,6 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { whenChildContentsActiveChanged={emptyFunction} focus={DocUtils.DefaultFocus} styleProvider={TabMinimapView.miniStyleProvider} - layerProvider={undefined} addDocTab={this.props.addDocTab} pinToPres={TabDocView.PinDoc} docFilters={CollectionDockingView.Instance.childDocFilters} diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 4707ebb80..f587dbbf6 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -21,7 +21,7 @@ } .treeView-bulletIcons { - // width: $TREE_BULLET_WIDTH; + // width: $TREE_BULLET_WIDTH; width: 100%; height: 100%; @@ -39,16 +39,17 @@ &:hover { .treeView-expandIcon { - display: unset; + display: unset; } } } + .treeView-bulletIcons:hover img { left: 14px; position: absolute; transform-origin: center left; transform: scale(6); - pointer-events:none; + pointer-events: none; } .bullet { @@ -56,14 +57,26 @@ width: $TREE_BULLET_WIDTH; color: $medium-gray; margin-top: 3px; - // transform: scale(1.3, 1.3); // bcz: why was this here? It makes displaying images in the treeView for documents that have icons harder. + // transform: scale(1.3, 1.3); // bcz: why was this here? It makes displaying images in the treeView for documents that have icons harder. border: #80808030 1px solid; border-radius: 4px; } } -.treeView-container-outline-active +.treeView-sorting { + position: absolute; + height: max-content; + pointer-events: none; + color: white; + border-radius: 4px; + font-size: 10px; +} + .treeView-container-active { + cursor: default; +} + +.treeView-container-outline-active .treeView-container-active { z-index: 100; position: relative; pointer-events: all; @@ -128,6 +141,7 @@ align-items: center; margin-left: 0.25rem; opacity: 0.75; + pointer-events: all; cursor: pointer; >svg { @@ -162,6 +176,7 @@ } .treeView-rightButtons { + >svg, .styleProvider-treeView-icon { display: inherit; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index f9497eb4a..3840afb04 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -26,11 +26,12 @@ import { DocumentView, DocumentViewInternal, DocumentViewProps, StyleProviderFun import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { StyleProp } from '../StyleProvider'; -import { CollectionTreeView } from './CollectionTreeView'; +import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; import { CollectionView, CollectionViewType } from './CollectionView'; import "./TreeView.scss"; import React = require("react"); import { KeyValueBox } from '../nodes/KeyValueBox'; +import { FieldViewProps } from '../nodes/FieldView'; export interface TreeViewProps { treeView: CollectionTreeView; @@ -71,7 +72,12 @@ export interface TreeViewProps { const treeBulletWidth = function () { return Number(TREE_BULLET_WIDTH.replace("px", "")); }; -@observer +export enum TreeSort { + Up = "up", + Down = "down", + Zindex = "z", + None = "none" +} /** * Renders a treeView of a collection of documents * @@ -79,6 +85,7 @@ const treeBulletWidth = function () { return Number(TREE_BULLET_WIDTH.replace("p * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree */ +@observer export class TreeView extends React.Component<TreeViewProps> { static _editTitleOnLoad: Opt<{ id: string, parent: TreeView | CollectionTreeView | undefined }>; static _openTitleScript: Opt<ScriptField | undefined>; @@ -131,7 +138,7 @@ export class TreeView extends React.Component<TreeViewProps> { // the selected item's index @computed get itemIndex() { return NumCast(this.doc._itemIndex); } // the item that's active - @computed get activeItem() { return Cast(this.childDocs[NumCast(this.doc._itemIndex)], Doc, null); } + @computed get activeItem() { return this.childDocs ? Cast(this.childDocs[NumCast(this.doc._itemIndex)], Doc, null) : undefined; } @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } childDocList(field: string) { @@ -170,6 +177,17 @@ export class TreeView extends React.Component<TreeViewProps> { docView.select(false); } } + @action + openLevel = (docView: DocumentView) => { + if (this.props.document.isFolder || Doc.IsSystem(this.props.document)) { + this.treeViewOpen = !this.treeViewOpen; + } else { + // choose an appropriate alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias + const bestAlias = docView.props.Document.author === Doc.CurrentUserEmail ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); + const nextBestAlias = DocListCast(this.props.document.aliases).find(doc => doc.author === Doc.CurrentUserEmail); + this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), "lightbox"); + } + } constructor(props: any) { super(props); @@ -245,7 +263,7 @@ export class TreeView extends React.Component<TreeViewProps> { layout: CollectionView.LayoutString("data"), title: "-title-", treeViewExpandedViewLock: true, treeViewExpandedView: "data", - _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: "outline", + _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: TreeViewType.outline, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _width: 1000, _height: 10 }); Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); @@ -424,14 +442,18 @@ export class TreeView extends React.Component<TreeViewProps> { @computed get renderContent() { 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 } }; if (["links", "annotations", "aliases", this.fieldKey].includes(expandKey)) { + const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); + const sortKeys = Object.keys(sortings); + const curSortIndex = Math.max(0, sortKeys.findIndex(val => val === sorting)); const key = (expandKey === "annotations" ? `${this.fieldKey}-` : "") + expandKey; const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { // if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't), // then the modification would be done here const ordering = StrCast(this.doc.treeViewSortCriterion); - if (ordering === "Z") { + if (ordering === TreeSort.Zindex) { const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering); doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000; docs.push(doc); @@ -444,29 +466,31 @@ export class TreeView extends React.Component<TreeViewProps> { const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); const docs = expandKey === "aliases" ? this.childAliases : expandKey === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs; let downX = 0, downY = 0; - const sortings = ["up", "down", "Z", undefined]; - const curSort = Math.max(0, sortings.indexOf(Cast(this.doc.treeViewSortCriterion, "string", null))); - return <ul key={expandKey + "more"} title={"sort: " + sortings[curSort]} className={this.doc.treeViewHideTitle ? "no-indent" : ""} - onPointerDown={e => { downX = e.clientX; downY = e.clientY; e.stopPropagation(); }} - onClick={(e) => { - if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { - !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortings[(curSort + 1) % sortings.length]); - e.stopPropagation(); - } - }}> - {!docs ? (null) : - TreeView.GetChildElements(docs, this.props.treeView, this, this.layoutDoc, - this.dataDoc, this.props.containerCollection, this.props.prevSibling, addDoc, remDoc, this.move, - StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, - this.props.isContentActive, this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, - [...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged, - this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(), - // TODO: [AL] add these - this.props.AddToMap, - this.props.RemFromMap, - this.props.hierarchyIndex, - )} - </ul >; + return <> + {!docs?.length ? (null) : <div className={'treeView-sorting'} style={{ background: sortings[sorting]?.color }} > + {sortings[sorting]?.label} + </div>} + <ul key={expandKey + "more"} title="click to change sort order" className={this.doc.treeViewHideTitle ? "no-indent" : ""} + onPointerDown={e => { downX = e.clientX; downY = e.clientY; e.stopPropagation(); }} + onClick={(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(); + } + }}> + {!docs ? (null) : + TreeView.GetChildElements(docs, this.props.treeView, this, this.layoutDoc, + this.dataDoc, this.props.containerCollection, this.props.prevSibling, addDoc, remDoc, this.move, + StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, + this.props.isContentActive, this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, + [...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged, + this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(), + // TODO: [AL] add these + this.props.AddToMap, + this.props.RemFromMap, + this.props.hierarchyIndex)} + </ul > + </>; } else if (this.treeViewExpandedView === "fields") { return <ul key={this.doc[Id] + this.doc.title}> <div style={{ display: "inline-block" }} > @@ -501,10 +525,15 @@ export class TreeView extends React.Component<TreeViewProps> { return <div className={`bullet${this.props.treeView.outlineMode ? "-outline" : ""}`} key={"bullet"} title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"} onClick={this.bulletClick} - style={this.props.treeView.outlineMode ? { opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity) } : { - color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), - opacity: checked === "unchecked" || typeof iconType !== "string" ? undefined : 0.4 - }}> + style={this.props.treeView.outlineMode ? + { + opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity) + } : + { + pointerEvents: this.props.isContentActive() ? "all" : undefined, + opacity: checked === "unchecked" || typeof iconType !== "string" ? undefined : 0.4, + color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), + }}> {this.props.treeView.outlineMode ? !(this.doc.text as RichTextField)?.Text ? (null) : <FontAwesomeIcon size="sm" icon={[this.childDocs?.length && !this.treeViewOpen ? "fas" : "far", "circle"]} /> : @@ -528,7 +557,7 @@ export class TreeView extends React.Component<TreeViewProps> { const data = () => this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : ""; const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases"; const fields = () => Doc.UserDoc().noviceMode ? "" : "fields"; - const layout = (this.props.treeView.dashboardMode && Doc.UserDoc().noviceMode) || this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"]; + const layout = (Doc.UserDoc().noviceMode) || this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"]; return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m); } @action @@ -543,9 +572,9 @@ export class TreeView extends React.Component<TreeViewProps> { @computed get headerElements() { return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? (null) : <> - {this.doc.hideContextMenu ? (null) : <FontAwesomeIcon key="bars" icon="bars" size="sm" onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} />} + {this.doc.hideContextMenu ? (null) : <FontAwesomeIcon title="context menu" key="bars" icon="bars" size="sm" onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} />} {this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? (null) : - <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}> + <span className="collectionTreeView-keyHeader" title="type of expanded data" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}> {this.treeViewExpandedView} </span>} </>; @@ -582,74 +611,6 @@ export class TreeView extends React.Component<TreeViewProps> { return this.props.onChildClick?.() ?? (ScriptField.MakeFunction(`DocFocusOrOpen(self)`)! || this._editTitleScript?.()) } - //Regular click zoom to document on canvas - @action - selectElement = async (doc: Doc) => { - const context = Cast(doc.context, Doc, null); - this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem); - if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0); - else this.updateCurrentPresentation(context); - - if (this.activeItem.setPosition && - this.activeItem.y !== undefined && - this.activeItem.x !== undefined && - this.targetDoc.x !== undefined && - this.targetDoc.y !== undefined) { - const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms)); - const time = 10; - const ydiff = NumCast(this.activeItem.y) - NumCast(this.targetDoc.y); - const xdiff = NumCast(this.activeItem.x) - NumCast(this.targetDoc.x); - - for (let i = 0; i < time; i++) { - this.targetDoc.x = NumCast(this.targetDoc.x) + xdiff / time; - this.targetDoc.y = NumCast(this.targetDoc.y) + ydiff / time; - await timer(0.1); - } - } - } - - static keyEventsWrapper = (e: KeyboardEvent) => { - PresBox.Instance.keyEvents(e); - } - - @action - updateCurrentPresentation = (pres?: Doc) => { - if (pres) Doc.UserDoc().activePresentation = pres; - else Doc.UserDoc().activePresentation = this.doc; - document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); - document.addEventListener("keydown", PresBox.keyEventsWrapper, true); - this._presKeyEventsActive = true; - // PresBox.Instance = this; - } - - //The function that is called when a document is clicked or reached through next or back. - //it'll also execute the necessary actions if presentation is playing. - public gotoDocument = action((index: number, from?: Doc, group?: boolean) => { - Doc.UnBrushAllDocs(); - if (index >= 0 && index < this.childDocs.length) { - this.doc._itemIndex = index; - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - - if (targetDoc) { - targetDoc && runInAction(() => { - if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0; - else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500; - }); - setTimeout(() => targetDoc.focusSpeed = 500, this.activeItem.presTransition ? NumCast(this.activeItem.presTransition) + 10 : 510); - } - if (targetDoc?.lastFrame !== undefined) { - targetDoc._currentFrame = 0; - } - if (!group) this._selectedArray.clear(); - this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //TODO: needs more work on selected arrayUpdate selected array - // if (this.layoutDoc._viewType === "stacking" && !group) this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list - // this.onHideDocument(); //Handles hide after/before - } - }); - - - onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); @@ -679,24 +640,29 @@ export class TreeView extends React.Component<TreeViewProps> { if (property.startsWith(StyleProp.Decorations)) return (null); return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView } - onKeyDown = (e: React.KeyboardEvent) => { - if (this.doc.treeViewHideHeader || this.props.treeView.outlineMode) { + onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { + if (this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) { switch (e.key) { case "Tab": - e.stopPropagation(); - e.preventDefault(); + e.stopPropagation?.(); + e.preventDefault?.(); setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150); - return UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true), "tab"); + UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true), "tab"); + return true; case "Backspace": - e.stopPropagation(); - e.preventDefault(); - return !(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc); + if (!(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc)) { + e.stopPropagation?.(); + e.preventDefault?.(); + return true; + } + break; case "Enter": - e.stopPropagation(); - e.preventDefault(); + e.stopPropagation?.(); + e.preventDefault?.(); return UndoManager.RunInBatch(this.makeTextCollection, "bullet"); } } + return false; } titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - 2 * treeBulletWidth())); @@ -743,7 +709,6 @@ export class TreeView extends React.Component<TreeViewProps> { hideDecorationTitle={this.props.treeView.outlineMode} hideResizeHandles={this.props.treeView.outlineMode} styleProvider={this.titleStyleProvider} - layerProvider={returnTrue} docViewPath={returnEmptyDoclist} treeViewDoc={this.props.treeView.props.Document} addDocument={undefined} @@ -835,13 +800,13 @@ export class TreeView extends React.Component<TreeViewProps> { onClick={this.onChildClick} focus={this.refocus} ContentScaling={returnOne} + onKey={this.onKeyDown} hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} ScreenToLocalTransform={this.docTransform} renderDepth={this.props.renderDepth + 1} treeViewDoc={this.props.treeView?.props.Document} rootSelected={returnTrue} - layerProvider={returnTrue} docViewPath={this.props.treeView.props.docViewPath} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} @@ -876,9 +841,9 @@ export class TreeView extends React.Component<TreeViewProps> { } @computed get renderBorder() { - const sorting = this.doc[`${this.fieldKey}-sortCriterion`]; - return <div className={`treeView-border${this.props.treeView.outlineMode ? "outline" : ""}`} - style={{ borderColor: sorting === undefined ? undefined : sorting === "up" ? "crimson" : sorting === "down" ? "blue" : "green" }}> + const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); + const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string, label: string } }; + return <div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ""}`} style={{ borderColor: sortings[sorting]?.color }}> {!this.treeViewOpen ? (null) : this.renderContent} </div>; } @@ -895,13 +860,13 @@ export class TreeView extends React.Component<TreeViewProps> { render() { TraceMobx(); - const hideTitle = this.doc.treeViewHideHeader || this.props.treeView.outlineMode; + const hideTitle = this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode; return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? "<" + this.doc.title + ">" : // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles <div className={`treeView-container${this.props.isContentActive() ? "-active" : ""}`} ref={this.createTreeDropTarget} onDrop={this.onTreeDrop} - //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document - onKeyDown={this.onKeyDown} + //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document + // onKeyDown={this.onKeyDown} > <li className="collection-child"> {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader ? // should test for prop 'treeViewRenderDocWithBulletAsHeader" @@ -913,11 +878,11 @@ export class TreeView extends React.Component<TreeViewProps> { public static sortDocs(childDocs: Doc[], criterion: string | undefined) { const docs = childDocs.slice(); - if (criterion) { + if (criterion !== TreeSort.None) { const sortAlphaNum = (a: string, b: string): 0 | 1 | -1 => { const reN = /[0-9]*$/; - const aA = a.replace(reN, ""); // get rid of trailing numbers - const bA = b.replace(reN, ""); + const aA = a.replace(reN, "") ? a.replace(reN, "") : +a; // get rid of trailing numbers + const bA = b.replace(reN, "") ? b.replace(reN, "") : +b; if (aA === bA) { // if header string matches, then compare numbers numerically const aN = parseInt(a.match(reN)![0], 10); const bN = parseInt(b.match(reN)![0], 10); @@ -927,10 +892,10 @@ export class TreeView extends React.Component<TreeViewProps> { } }; docs.sort(function (d1, d2): 0 | 1 | -1 { - const a = (criterion === "up" ? d2 : d1); - const b = (criterion === "up" ? d1 : d2); - const first = a[criterion === "Z" ? "zIndex" : "title"]; - const second = b[criterion === "Z" ? "zIndex" : "title"]; + const a = (criterion === TreeSort.Up ? d2 : d1); + const b = (criterion === TreeSort.Up ? d1 : d2); + const first = a[criterion === TreeSort.Zindex ? "zIndex" : "title"]; + const second = b[criterion === TreeSort.Zindex ? "zIndex" : "title"]; if (typeof first === 'number' && typeof second === 'number') return (first - second) > 0 ? 1 : -1; if (typeof first === 'string' && typeof second === 'string') return sortAlphaNum(first, second); return criterion ? 1 : -1; @@ -978,7 +943,7 @@ export class TreeView extends React.Component<TreeViewProps> { childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); } - const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion)); + const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None)); const rowWidth = () => panelWidth() - treeBulletWidth(); const treeViewRefs = new Map<Doc, TreeView | undefined>(); return docs.filter(child => child instanceof Doc).map((child, i) => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 16c7df311..9de2cfcf9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -147,7 +147,7 @@ export function computePivotLayout( const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>(); let nonNumbers = 0; - const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField); + const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || "author"; childPairs.map(pair => { const lval = pivotFieldKey === "#" || pivotFieldKey === "tags" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) : Cast(pair.layout[pivotFieldKey], listSpec("string"), null); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f0b3d70a0..09bb1526e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -3,16 +3,16 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import { DateField } from "../../../../fields/DateField"; -import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; +import { DataSym, Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { ObjectField } from "../../../../fields/ObjectField"; import { RichTextField } from "../../../../fields/RichTextField"; -import { ImageField } from "../../../../fields/URLField"; import { createSchema, listSpec } from "../../../../fields/Schema"; import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; +import { ImageField } from "../../../../fields/URLField"; import { TraceMobx } from "../../../../fields/util"; import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils"; @@ -26,6 +26,7 @@ import { DragManager, dropActionType } from "../../../util/DragManager"; import { HistoryUtil } from "../../../util/History"; import { InteractionUtils } from "../../../util/InteractionUtils"; import { LinkManager } from "../../../util/LinkManager"; +import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; import { SearchUtil } from "../../../util/SearchUtil"; import { SelectionManager } from "../../../util/SelectionManager"; import { ColorScheme } from "../../../util/SettingsManager"; @@ -44,15 +45,17 @@ import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { PresBox } from "../../nodes/trails/PresBox"; import { VideoBox } from "../../nodes/VideoBox"; import { CreateImage } from "../../nodes/WebBoxRenderer"; -import { StyleLayers, StyleProp } from "../../StyleProvider"; +import { StyleProp } from "../../StyleProvider"; import { CollectionDockingView } from "../CollectionDockingView"; import { CollectionSubView } from "../CollectionSubView"; +import { TreeViewType } from "../CollectionTreeView"; import { CollectionViewType } from "../CollectionView"; import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import { FieldView, FieldViewProps } from "../../nodes/FieldView"; export const panZoomSchema = createSchema({ _panX: "number", @@ -75,6 +78,7 @@ export type collectionFreeformViewProps = { scaleField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; + dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not. // However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents. }; @@ -121,8 +125,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } - @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } - @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && this.props.isContentActive(); } @computed get fitToContentVals() { return { bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 }, @@ -155,6 +157,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action setKeyFrameEditing = (set: boolean) => this._keyframeEditing = set; getKeyFrameEditing = () => this._keyframeEditing; + onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; @@ -221,7 +224,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) { - if (!de.embedKey && !this.ChildDrag && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false; + if (!de.embedKey && !this.ChildDrag && this.rootDoc._isGroup) return false; if (!super.onInternalDrop(e, de)) return false; const refDoc = docDragData.droppedDocuments[0]; const [xpo, ypo] = this.getContainerTransform().transformPoint(de.x, de.y); @@ -250,7 +253,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)]; layoutDoc._width = NumCast(layoutDoc._width, 300); layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300); - !StrListCast(d._layerTags).includes(StyleLayers.Background) && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront + (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront } (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments); @@ -278,7 +281,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.context !== this.props.Document) { // if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" }); this.props.addDocument?.(source); - de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed + de.complete.linkDocument = DocUtils.MakeLink({ doc: linkDragData.linkSourceGetAnchor() }, { doc: source }, "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; @@ -417,8 +420,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection styleProp = colors[cluster % colors.length]; const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set?.filter(s => !StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); - set?.filter(s => StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); + set?.map(s => styleProp = StrCast(s.backgroundColor)); } } //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray"; return styleProp; @@ -623,7 +625,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } onClick = (e: React.MouseEvent) => { - if ((Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) { + if (this.onBrowseClickHandler()) { + if (this.props.DocumentView?.()) { + this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView(), clientX: e.clientX, clientY: e.clientY }); + } + e.stopPropagation(); + e.preventDefault(); + } + else if ((Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) { if (e.shiftKey) { if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a shift + double click this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1); @@ -950,7 +959,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action onPointerWheel = (e: React.WheelEvent): void => { - if (this.layoutDoc._lockedTransform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.OverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === "outline") return; + if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.OverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -1016,7 +1025,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action bringToFront = (doc: Doc, sendToBack?: boolean) => { - if (sendToBack || StrListCast(doc._layerTags).includes(StyleLayers.Background)) { + if (sendToBack) { doc.zIndex = 0; } else if (doc.isInkMask) { doc.zIndex = 5000; @@ -1068,21 +1077,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.props.focus(doc, options); } else { const xfToCollection = options?.docTransform ?? Transform.Identity(); - const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey] }; + const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoom ? this.Document[this.scaleFieldKey] : undefined }; const newState = HistoryUtil.getState(); - const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); + const cantTransform = /*this.props.isAnnotationOverlay ||*/ ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined); if (!cantTransform) { // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection newState.initializers![this.Document[Id]] = { panX: panX, panY: panY }; HistoryUtil.pushState(newState); } // focus on the document in the collection - const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== undefined); + const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale); const focusSpeed = options?.instant ? 0 : didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0; // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... if (didMove) { - this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow scale && (this.Document[this.scaleFieldKey] = scale); + this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow } const startTime = Date.now(); @@ -1119,23 +1128,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => { const layoutdoc = Doc.Layout(doc); - const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1); - const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1); const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y)); const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]()); - const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1] }; - const cx = NumCast(this.layoutDoc._panX); - const cy = NumCast(this.layoutDoc._panY); - const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 }; + const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] }; if (scale) { - const maxZoom = 2; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context + const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context + const newScale = Math.min(maxZoom, 1 / (this.contentScaling || 1) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height))); return { - panX: (bounds.left + bounds.right) / 2, - panY: (bounds.top + bounds.bot) / 2, - scale: Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1]))) + panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2, + panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2, + scale: newScale }; } + const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1); + const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1); + const cx = NumCast(this.layoutDoc._panX); + const cy = NumCast(this.layoutDoc._panY); + const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 }; if ((screen.right - screen.left) < (bounds.right - bounds.left) || (screen.bot - screen.top) < (bounds.bot - bounds.top)) { return { @@ -1152,10 +1162,37 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + @undoBatch + @action + onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { + const docView = fieldProps.DocumentView?.(); + if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._singleLine) && ["Tab", "Enter"].includes(e.key)) { + e.stopPropagation?.(); + const below = !e.altKey && e.key !== "Tab"; + const layoutKey = StrCast(docView.LayoutFieldKey); + const newDoc = Doc.MakeCopy(docView.rootDoc, true); + const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)]; + newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined; + if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10; + else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10; + if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) { + newDoc[layoutKey] = docView.rootDoc[layoutKey]; + } + Doc.GetProto(newDoc).text = undefined; + FormattedTextBox.SelectOnLoad = newDoc[Id]; + return this.addDocument?.(newDoc); + } + } + pointerEvents = () => { + const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); + const pointerEvents = this.props.isContentActive() === false ? "none" : + this.props.childPointerEvents ? "all" : + (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents?.(); + return pointerEvents; + } getChildDocView(entry: PoolData) { const childLayout = entry.pair.layout; const childData = entry.pair.data; - const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); return <CollectionFreeFormDocumentView key={childLayout[Id] + (entry.replica || "")} DataDoc={childData} Document={childLayout} @@ -1171,15 +1208,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString} rootSelected={childData ? this.rootSelected : returnFalse} onClick={this.onChildClickHandler} + onKey={this.onKeyDown} onDoubleClick={this.onChildDoubleClickHandler} + onBrowseClick={this.onBrowseClickHandler} ScreenToLocalTransform={childLayout.z ? this.getContainerTransform : this.getTransform} PanelWidth={childLayout[WidthSym]} PanelHeight={childLayout[HeightSym]} docFilters={this.childDocFilters} docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} + isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive} isContentActive={emptyFunction} - isDocumentActive={this.props.childDocumentsActive ? this.props.isDocumentActive : this.isContentActive} focus={this.focusDocument} addDocTab={this.addDocTab} addDocument={this.props.addDocument} @@ -1189,16 +1228,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} docViewPath={this.props.docViewPath} styleProvider={this.getClusterColor} - layerProvider={this.props.layerProvider} dataProvider={this.childDataProvider} sizeProvider={this.childSizeProvider} - freezeDimensions={this.props.childFreezeDimensions} + freezeDimensions={BoolCast(this.props.Document.childFreezeDimensions, this.props.childFreezeDimensions)} dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} bringToFront={this.bringToFront} showTitle={this.props.childShowTitle} + dontScaleFilter={this.props.dontScaleFilter} dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView} - pointerEvents={this.props.isContentActive() === false ? "none" : this.backgroundActive || this.props.childPointerEvents ? "all" : - (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents} + pointerEvents={this.pointerEvents} jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0} //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this />; @@ -1422,19 +1460,39 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection })); } + replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => { + if (oldDiv.childNodes && newDiv) { + for (let i = 0; i < oldDiv.childNodes.length; i++) { + this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement); + } + } + if (oldDiv instanceof HTMLCanvasElement) { + const canvas = oldDiv; + const img = document.createElement('img'); // create a Image Element + img.src = canvas.toDataURL(); //image source + img.style.width = canvas.style.width; + img.style.height = canvas.style.height; + const newCan = newDiv as HTMLCanvasElement; + const parEle = newCan.parentElement as HTMLElement; + parEle.removeChild(newCan); + parEle.appendChild(img); + } + } + updateIcon = () => { - this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString(); - this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString(); - const htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!); - this.props.docViewPath().lastElement().ContentDiv!.style.width = ""; - this.props.docViewPath().lastElement().ContentDiv!.style.height = ""; + const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; + const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; + newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); + newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); + this.replaceCanvases(docViewContent, newDiv); + const htmlString = this._mainCont && new XMLSerializer().serializeToString(newDiv); const nativeWidth = this.layoutDoc[WidthSym](); const nativeHeight = this.layoutDoc[HeightSym](); CreateImage( "", document.styleSheets, - htmlString?.replace(/docView-hack/g, 'documentView-hack'), + htmlString, nativeWidth, nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), NumCast(this.layoutDoc._scrollTop) @@ -1526,7 +1584,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const viewctrls = ContextMenu.Instance.findByDescription("UI Controls..."); const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : []; - !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null; !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null; !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" }); @@ -1538,7 +1595,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); if (!Doc.UserDoc().noviceMode) { optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); - optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" }); } !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); const mores = ContextMenu.Instance.findByDescription("More..."); @@ -1589,7 +1645,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => intersectRect(docDims(doc), rect); const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; - let snappableDocs = activeDocs.filter(doc => !StrListCast(doc._layerTags).includes(StyleLayers.Background) && doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to + let snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs @@ -1714,12 +1770,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onContextMenu={this.onContextMenu} style={{ pointerEvents: this.props.Document.type === DocumentType.MARKER ? "none" : // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach. - this.backgroundEvents ? "all" : this.props.pointerEvents as any, + (SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())) ? "all" : this.props.pointerEvents?.() as any, transform: `scale(${this.contentScaling || 1})`, width: `${100 / (this.contentScaling || 1)}%`, height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}> - {this._firstRender || (this.Document._freeformLOD && !this.props.isContentActive() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0) ? + {this._firstRender ? this.placeholder : this.marqueeView} {this.props.noOverlay ? (null) : <CollectionFreeFormOverlayView elements={this.elementFunc} />} @@ -1742,7 +1798,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection </svg> </div>} - {this.props.Document._isGroup && SnappingManager.GetIsDragging() && (this.ChildDrag || this.props.layerProvider?.(this.props.Document) === false) ? + {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? <div className="collectionFreeForm-groupDropper" ref={this.createGroupEventsTarget} style={{ width: this.ChildDrag ? "10000" : "100%", height: this.ChildDrag ? "10000" : "100%", @@ -1951,4 +2007,21 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor } }} />; } -}
\ No newline at end of file +} + +export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) { + SelectionManager.DeselectAll(); + dv.props.focus(dv.props.Document, { + willZoom: true, afterFocus: async (didMove) => { + if (!didMove) { + const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; + const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || "_viewScale"] !== 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); + } + return ViewAdjustment.doNothing; + } + }); + Doc.linkFollowHighlight(dv?.props.Document, false); +} +ScriptingGlobals.add(CollectionBrowseClick);
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index afdb91633..0338fcb18 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,14 +1,15 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc"; +import { AclAdmin, AclAugment, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { RichTextField } from "../../../../fields/RichTextField"; import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; +import { ImageField } from "../../../../fields/URLField"; import { GetEffectiveAcl } from "../../../../fields/util"; -import { Utils, intersectRect, returnFalse } from "../../../../Utils"; +import { intersectRect, returnFalse, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; @@ -21,19 +22,15 @@ import { ContextMenu } from "../../ContextMenu"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { PresBox } from "../../nodes/trails/PresBox"; import { PresMovement } from "../../nodes/trails/PresEnums"; +import { VideoBox } from "../../nodes/VideoBox"; +import { pasteImageBitmap } from "../../nodes/WebBoxRenderer"; import { PreviewCursor } from "../../PreviewCursor"; import { CollectionDockingView } from "../CollectionDockingView"; import { SubCollectionViewProps } from "../CollectionSubView"; -import { CollectionView } from "../CollectionView"; -import { CollectionStackingViewFieldColumn } from "../CollectionStackingViewFieldColumn"; +import { TreeView } from "../TreeView"; import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu"; import "./MarqueeView.scss"; import React = require("react"); -import { StyleLayers } from "../../StyleProvider"; -import { TreeView } from "../TreeView"; -import { VideoBox } from "../../nodes/VideoBox"; -import { ImageField, WebField } from "../../../../fields/URLField"; -import { pasteImageBitmap } from "../../nodes/WebBoxRenderer"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -356,7 +353,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this.hideMarquee(); } - getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, layers: string[], makeGroup: Opt<boolean>) => { + getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, makeGroup: Opt<boolean>) => { const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => { Doc.GetProto(doc).data = new List<Doc>(selected); Doc.GetProto(doc).title = makeGroup ? "grouping" : "nested freeform"; @@ -365,7 +362,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque return doc; })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); newCollection.system = undefined; - newCollection._layerTags = new List<string>(layers); newCollection._width = this.Bounds.width; newCollection._height = this.Bounds.height; newCollection._isGroup = makeGroup; @@ -480,7 +476,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque })); this.props.removeDocument?.(selected); } - const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined, [], group); + const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined, group); this.props.addDocument?.(newCollection); this.props.selectDocuments([newCollection]); MarqueeOptionsMenu.Instance.fadeOut(true); @@ -564,7 +560,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque }); const summary = Docs.Create.TextDocument("", { _backgroundColor: "#e2ad32", x: this.Bounds.left, y: this.Bounds.top, isPushpin: true, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" }); const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: "transparent" }); - DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing", ""); + DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summary of:summarized by", ""); portal.hidden = true; this.props.addDocument?.(portal); @@ -574,7 +570,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @action background = (e: KeyboardEvent | React.PointerEvent | undefined) => { - const newCollection = this.getCollection([], undefined, [StyleLayers.Background], undefined); + const newCollection = this.getCollection([], undefined, undefined); this.props.addDocument?.(newCollection); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); @@ -660,7 +656,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque (this.touchesLine(bounds) || this.boundingShape(bounds)) && selection.push(doc); } }; - this.props.activeDocuments().filter(doc => this.props.layerProvider?.(doc) !== false && !doc.z).map(selectFunc); + this.props.activeDocuments().filter(doc => !doc.z && !doc._lockedPosition).map(selectFunc); if (!selection.length && selectBackgrounds) this.props.activeDocuments().filter(doc => doc.z === undefined).map(selectFunc); if (!selection.length) this.props.activeDocuments().filter(doc => doc.z !== undefined).map(selectFunc); return selection; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index e3d4f86c1..c0a33a5e0 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -151,11 +151,10 @@ export class CollectionLinearView extends CollectionSubView() { removeDocument={this.props.removeDocument} ScreenToLocalTransform={docXf} PanelWidth={nested ? doc[WidthSym] : this.dimension} - PanelHeight={nested ? doc[HeightSym] : this.dimension} + PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension} renderDepth={this.props.renderDepth + 1} focus={emptyFunction} styleProvider={this.props.styleProvider} - layerProvider={this.props.layerProvider} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} @@ -231,7 +230,7 @@ export class CollectionLinearView extends CollectionSubView() { <span className="bottomPopup-text"> Currently playing: {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => - <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true)}> + <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? "" : ",")} </span>)} </span> diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 6929a1cd8..e2dfb25e2 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -1,10 +1,10 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from "react"; -import { Doc } from '../../../../fields/Doc'; +import { Doc, DocListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse } from '../../../../Utils'; +import { returnFalse } from '../../../../Utils'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; @@ -194,11 +194,38 @@ export class CollectionMulticolumnView extends CollectionSubView() { @undoBatch @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { - if (super.onInternalDrop(e, de)) { + let dropInd = -1; + if (de.complete.docDragData && this._mainCont) { + let curInd = -1; de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { - d._dimUnit = "*"; - d._dimMagnitude = 1; + 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) { + if (curInd !== -1 && curInd === Math.floor(index / 2)) { + dropInd = curInd; + } + else if (child.className === "multiColumnResizer") { + dropInd = Math.floor(index / 2); + } else { + dropInd = Math.ceil(index / 2 + (de.x - brect.x > brect.width / 2 ? 0 : -1)); + } + } + }); + if (super.onInternalDrop(e, de)) { + de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { + d._dimUnit = "*"; + d._dimMagnitude = 1; + if (dropInd !== curInd || dropInd === -1) { + if (this.childDocs.includes(d)) { + if (dropInd > this.childDocs.indexOf(d)) dropInd--; + } + Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d); + Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1); + } + })); + } } return false; } @@ -214,24 +241,30 @@ export class CollectionMulticolumnView extends CollectionSubView() { } return this.props.addDocTab(doc, where); } + isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + isChildContentActive = () => this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return <DocumentView Document={layout} DataDoc={layout.resolvedDataDoc as Doc} styleProvider={this.props.styleProvider} - layerProvider={this.props.layerProvider} docViewPath={this.props.docViewPath} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} freezeDimensions={this.props.childFreezeDimensions} renderDepth={this.props.renderDepth + 1} - isContentActive={emptyFunction} + isContentActive={this.isChildContentActive} + isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive} + hideResizeHandles={this.props.childHideResizeHandles?.()} + hideDecorationTitle={this.props.childHideDecorationTitle?.()} + fitContentsToDoc={this.props.fitContentsToDoc} PanelWidth={width} PanelHeight={height} rootSelected={this.rootSelected} dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} + suppressSetHeight={true} ScreenToLocalTransform={dxf} focus={this.props.focus} docFilters={this.childDocFilters} @@ -290,13 +323,13 @@ export class CollectionMulticolumnView extends CollectionSubView() { render(): JSX.Element { return ( - <div className={"collectionMulticolumnView_contents"} + <div className={"collectionMulticolumnView_contents"} ref={this.createDashEventsTarget} style={{ width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`, height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`, marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin), marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin) - }} ref={this.createDashEventsTarget}> + }} > {this.contents} </div> ); diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 7e2b83230..3010e36aa 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -1,10 +1,10 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from "react"; -import { Doc } from '../../../../fields/Doc'; +import { Doc, DocListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse } from '../../../../Utils'; +import { returnFalse } from '../../../../Utils'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; @@ -190,14 +190,42 @@ export class CollectionMultirowView extends CollectionSubView() { return Transform.Identity(); // type coersion, this case should never be hit } + @undoBatch @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { - if (super.onInternalDrop(e, de)) { + let dropInd = -1; + if (de.complete.docDragData && this._mainCont) { + let curInd = -1; de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { - d._dimUnit = "*"; - d._dimMagnitude = 1; + 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) { + if (curInd !== -1 && curInd === Math.floor(index / 2)) { + dropInd = curInd; + } + else if (child.className === "multiColumnResizer") { + dropInd = Math.floor(index / 2); + } else { + dropInd = Math.ceil(index / 2 + (de.y - brect.y > brect.height / 2 ? 0 : -1)); + } + } + }); + if (super.onInternalDrop(e, de)) { + de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { + d._dimUnit = "*"; + d._dimMagnitude = 1; + if (dropInd !== curInd || dropInd === -1) { + if (this.childDocs.includes(d)) { + if (dropInd > this.childDocs.indexOf(d)) dropInd--; + } + Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d); + Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1); + } + })); + } } return false; } @@ -213,12 +241,13 @@ export class CollectionMultirowView extends CollectionSubView() { } return this.props.addDocTab(doc, where); } - getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { + isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + isChildContentActive = () => this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; + getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return <DocumentView Document={layout} DataDoc={layout.resolvedDataDoc as Doc} styleProvider={this.props.styleProvider} - layerProvider={this.props.layerProvider} docViewPath={this.props.docViewPath} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} @@ -231,9 +260,13 @@ export class CollectionMultirowView extends CollectionSubView() { onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} ScreenToLocalTransform={dxf} + isContentActive={this.isChildContentActive} + isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive} + hideResizeHandles={this.props.childHideResizeHandles?.()} + hideDecorationTitle={this.props.childHideDecorationTitle?.()} + fitContentsToDoc={this.props.fitContentsToDoc} focus={this.props.focus} docFilters={this.childDocFilters} - isContentActive={emptyFunction} docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} ContainingCollectionDoc={this.props.CollectionView?.props.Document} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index c2bb3b3ac..adcd9e1e3 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -199,7 +199,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc); const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null); // Jump to the this document - DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext, + DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext ? [targetContext] : [], undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc)); } } diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx index dc35b5749..0875c80b3 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx @@ -278,6 +278,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { @undoBatch onKeyDown = (e: React.KeyboardEvent): void => { if (e.key === "Enter") { + e.stopPropagation(); if (this._searchTerm.includes(":")) { const colpos = this._searchTerm.indexOf(":"); const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length); @@ -490,7 +491,9 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { <div className="keys-dropdown" style={{ zIndex: 1, width: this.props.width, maxWidth: this.props.width }}> <input className="keys-search" style={{ width: "100%" }} - ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown} + ref={this._inputRef} type="text" + value={this._searchTerm} placeholder="Column key" + onKeyDown={this.onKeyDown} onChange={e => this.onChange(e.target.value)} onClick={(e) => { e.stopPropagation(); this._inputRef.current?.focus(); }} onFocus={this.onFocus} ></input> diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index a93762ea4..b731479a5 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -417,7 +417,6 @@ export class CollectionSchemaView extends CollectionSubView() { docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} styleProvider={DefaultStyleProvider} - layerProvider={undefined} docViewPath={returnEmptyDoclist} ContainingCollectionDoc={this.props.CollectionView?.props.Document} ContainingCollectionView={this.props.CollectionView} diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index 605481ddf..bea5b3be6 100644 --- a/src/client/views/collections/collectionSchema/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -573,7 +573,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> { Document={this._showDoc} DataDoc={this._showDataDoc} styleProvider={DefaultStyleProvider} - layerProvider={undefined} docViewPath={returnEmptyDoclist} freezeDimensions={true} focus={DocUtils.DefaultFocus} |
