aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/goldenLayout.js32
-rw-r--r--src/client/util/DragManager.ts25
-rw-r--r--src/client/views/DocumentDecorations.tsx7
-rw-r--r--src/client/views/GlobalKeyHandler.ts1
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx109
-rw-r--r--src/client/views/collections/TabDocView.tsx3
6 files changed, 108 insertions, 69 deletions
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index 896237e1d..295875ef6 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -366,6 +366,7 @@
this._nOriginalY = 0;
this._bDragging = false;
+ this._bAborting = false;
this._fMove = lm.utils.fnBind(this.onMouseMove, this);
this._fUp = lm.utils.fnBind(this.onMouseUp, this);
@@ -427,6 +428,24 @@
}
},
+ AbortDrag: function () {
+ if (this._timeout != null) {
+ clearTimeout(this._timeout);
+ this._eBody.removeClass('lm_dragging');
+ this._eElement.removeClass('lm_dragging');
+ this._oDocument.find('iframe').css('pointer-events', '');
+ this._oDocument.unbind('mousemove touchmove', this._fMove);
+ this._oDocument.unbind('mouseup touchend', this._fUp);
+
+ if (this._bDragging === true) {
+ this._bDragging = false;
+ this._bAborting = true;
+ this.emit('dragStop', { pageX: 0, pageY: 0 }, this._nOriginalX + this._nX);
+ this._bAborting = false;
+ }
+ }
+ },
+
onMouseUp: function (oEvent) {
if (this._timeout != null) {
clearTimeout(this._timeout);
@@ -2178,18 +2197,18 @@
*/
_onDrop: function () {
this._layoutManager.dropTargetIndicator.hide();
-
+ let abortedDrop = this._dragListener._bAborting;
/*
* Valid drop area found
*/
- if (this._area !== null) {
+ if (!abortedDrop && this._area !== null) {
this._area.contentItem._$onDrop(this._contentItem, this._area);
/**
* No valid drop area available at present, but one has been found before.
* Use it
*/
- } else if (this._lastValidArea !== null) {
+ } else if (!abortedDrop && this._lastValidArea !== null) {
this._lastValidArea.contentItem._$onDrop(this._contentItem, this._lastValidArea);
/**
@@ -2197,7 +2216,7 @@
* content item to its original position if a original parent is provided.
* (Which is not the case if the drag had been initiated by createDragSource)
*/
- } else if (this._originalParent) {
+ } else if (!abortedDrop && this._originalParent) {
this._originalParent.addChild(this._contentItem);
/**
@@ -2212,7 +2231,7 @@
restoreScrollTops(this._contentItem.element)
this.element.remove();
- this._layoutManager.emit('itemDropped', this._contentItem);
+ !abortedDrop && this._layoutManager.emit('itemDropped', this._contentItem);
},
/**
@@ -2963,7 +2982,7 @@
if (this.contentItem.parent.isMaximised === true) {
this.contentItem.parent.toggleMaximise();
}
- new lm.controls.DragProxy(
+ let proxy = new lm.controls.DragProxy(
x,
y,
this._dragListener,
@@ -2971,6 +2990,7 @@
this.contentItem,
this.header.parent
);
+ this._layoutManager.emit('dragStart', proxy);
},
/**
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0d6a77f71..411fc6d11 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,3 +1,4 @@
+import { extend } from "lodash";
import { action } from "mobx";
import { DateField } from "../../fields/DateField";
import { Doc, Field, Opt } from "../../fields/Doc";
@@ -56,12 +57,7 @@ export function SetupDrag(
if (e.shiftKey) {
e.persist();
const dragDoc = await docFunc();
- dragDoc && DragManager.StartWindowDrag?.({
- pageX: e.pageX,
- pageY: e.pageY,
- preventDefault: emptyFunction,
- button: 0
- }, [dragDoc]);
+ dragDoc && DragManager.StartWindowDrag?.(e, [dragDoc]);
} else {
document.addEventListener("pointermove", onRowMove);
document.addEventListener("pointerup", onRowUp);
@@ -74,7 +70,8 @@ export function SetupDrag(
export namespace DragManager {
let dragDiv: HTMLDivElement;
let dragLabel: HTMLDivElement;
- export let StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined;
+ export let StartWindowDrag: Opt<((e: { pageX: number, pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => void)>;
+ export let CompleteWindowDrag: Opt<(aborted: boolean) => void>;
export function Root() {
const root = document.getElementById("root");
@@ -460,17 +457,13 @@ export namespace DragManager {
}
if (((e.target as any)?.className === "lm_tabs" || e?.shiftKey) && dragData.draggedDocuments.length === 1) {
dragData.dropAction = dragData.userDropAction || "same";
- if (dragData.dropAction === "move" || dragData.dropAction === "same") {
- dragData.removeDocument?.(dragData.draggedDocuments[0]);
- }
AbortDrag();
await finishDrag?.(new DragCompleteEvent(true, dragData));
- DragManager.StartWindowDrag?.({
- pageX: e.pageX,
- pageY: e.pageY,
- preventDefault: emptyFunction,
- button: 0
- }, dragData.droppedDocuments);
+ DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, (aborted) => {
+ if (!aborted && (dragData.dropAction === "move" || dragData.dropAction === "same")) {
+ dragData.removeDocument?.(dragData.draggedDocuments[0]);
+ }
+ });
}
const target = document.elementFromPoint(e.x, e.y);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8cd646935..d7ead713a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -151,12 +151,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
}
onMaximizeDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, () => {
- DragManager.StartWindowDrag?.({
- pageX: e.pageX,
- pageY: e.pageY,
- preventDefault: emptyFunction,
- button: 0
- }, [SelectionManager.Views().slice(-1)[0].rootDoc]);
+ DragManager.StartWindowDrag?.(e, [SelectionManager.Views().slice(-1)[0].rootDoc]);
return true;
}, emptyFunction, this.onMaximizeClick, false, false);
}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 1a4080d81..4a327a842 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -114,6 +114,7 @@ export class KeyManager {
DocumentLinksButton.StartLinkView = undefined;
InkStrokeProperties.Instance._controlButton = false;
CurrentUserUtils.SelectedTool = InkTool.None;
+ DragManager.CompleteWindowDrag?.(true);
var doDeselect = true;
if (SnappingManager.GetIsDragging()) {
DragManager.AbortDrag();
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 9e8374605..6931d9896 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -11,6 +11,7 @@ import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { inheritParentAcls } from '../../../fields/util';
+import { emptyFunction } from '../../../Utils';
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -46,7 +47,7 @@ export class CollectionDockingView extends CollectionSubView() {
private _reactionDisposer?: IReactionDisposer;
private _lightboxReactionDisposer?: IReactionDisposer;
private _containerRef = React.createRef<HTMLDivElement>();
- private _flush: UndoManager.Batch | undefined;
+ public _flush: UndoManager.Batch | undefined;
private _ignoreStateChange = "";
public tabMap: Set<any> = new Set();
public get initialized() { return this._goldenLayout !== null; }
@@ -62,15 +63,37 @@ export class CollectionDockingView extends CollectionSubView() {
DragManager.StartWindowDrag = this.StartOtherDrag;
}
- public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
- !this._flush && (this._flush = UndoManager.StartBatch("golden layout drag"));
+ /**
+ * Switches from dragging a document around a freeform canvas to dragging it as a tab to be docked.
+ *
+ * @param e fake mouse down event position data containing pageX and pageY coordinates
+ * @param dragDocs the documents to be dragged
+ * @param batch optionally an undo batch that has been started to use instead of starting a new batch
+ */
+ public StartOtherDrag = (e: { pageX: number, pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => {
+ this._flush = this._flush ?? UndoManager.StartBatch("golden layout drag");
const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) :
- { type: 'row', content: dragDocs.map((doc, i) => CollectionDockingView.makeDocumentConfig(doc)) };
+ { type: 'row', content: dragDocs.map(doc => CollectionDockingView.makeDocumentConfig(doc)) };
const dragSource = this._goldenLayout.createDragSource(document.createElement("div"), config);
- //dragSource._dragListener.on("dragStop", dragSource.destroy);
- dragSource._dragListener.onMouseDown(e);
+ this.tabDragStart(dragSource, finishDrag);
+ dragSource._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 });
}
+ tabItemDropped = () => DragManager.CompleteWindowDrag?.(false);
+ tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => {
+ DragManager.CompleteWindowDrag = (aborted: boolean) => {
+ if (aborted) {
+ proxy._dragListener.AbortDrag();
+ if (this._flush) {
+ this._flush.cancel(); // cancel the undo change being logged
+ this._flush = undefined;
+ this.setupGoldenLayout(); // restore golden layout to where it was before the drag (this is a no-op when using StartOtherDrag because the proxy dragged item was never in the golden layout)
+ }
+ DragManager.CompleteWindowDrag = undefined;
+ }
+ finishDrag?.(aborted);
+ }
+ }
@undoBatch
public CloseFullScreen = () => {
this._goldenLayout._maximisedItem?.toggleMaximise();
@@ -106,7 +129,6 @@ export class CollectionDockingView extends CollectionSubView() {
docconfig.callDownwards('_$init');
instance._goldenLayout._$maximiseItem(docconfig);
instance._goldenLayout.emit('stateChanged');
- instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig());
instance.stateChanged();
return true;
}
@@ -255,7 +277,6 @@ export class CollectionDockingView extends CollectionSubView() {
layoutChanged() {
this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
this._goldenLayout.emit('stateChanged');
- this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
this.stateChanged();
return true;
}
@@ -294,6 +315,9 @@ export class CollectionDockingView extends CollectionSubView() {
}
}
this._goldenLayout.init();
+ this._goldenLayout.root.layoutManager.on('itemDropped', this.tabItemDropped);
+ this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart)
+ this._goldenLayout.root.layoutManager.on('activeContentItemChanged', this.stateChanged);
}
}
@@ -335,13 +359,12 @@ export class CollectionDockingView extends CollectionSubView() {
@action
onPointerUp = (e: MouseEvent): void => {
window.removeEventListener("pointerup", this.onPointerUp);
- if (this._flush) {
- setTimeout(() => {
- CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
- this.stateChanged();
- this._flush?.end();
- this._flush = undefined;
- }, 10);
+ const flush = this._flush;
+ this._flush = undefined;
+ if (flush) {
+ DragManager.CompleteWindowDrag = undefined;
+ if (!this.stateChanged()) flush.cancel();
+ else flush.end();
}
}
@@ -352,9 +375,12 @@ export class CollectionDockingView extends CollectionSubView() {
hitFlyout = (par.className === "dockingViewButtonSelector");
}
if (!hitFlyout) {
+ const htmlTarget = e.target as HTMLElement;
window.addEventListener("mouseup", this.onPointerUp);
- if (!(e.target as HTMLElement).closest("*.lm_content") && ((e.target as HTMLElement).closest("*.lm_tab") || (e.target as HTMLElement).closest("*.lm_stack"))) {
- this._flush = UndoManager.StartBatch("golden layout edit");
+ if (!htmlTarget.closest("*.lm_content") && (htmlTarget.closest("*.lm_tab") || htmlTarget.closest("*.lm_stack"))) {
+ if (htmlTarget.className !== "lm_close_tab") {
+ this._flush = UndoManager.StartBatch("golden layout edit");
+ }
}
}
if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) &&
@@ -388,38 +414,43 @@ export class CollectionDockingView extends CollectionSubView() {
@action
stateChanged = () => {
+ this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
const json = JSON.stringify(this._goldenLayout.toConfig());
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);
-
- 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);
+ const changesMade = this.props.Document.dockingConfig !== 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);
+ }
+ return changesMade;
}
tabDestroyed = (tab: any) => {
this.tabMap.delete(tab);
tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
+ this.stateChanged();
}
tabCreated = (tab: any) => {
tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 8b5022593..ad5c0efb3 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -190,7 +190,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true);
SelectionManager.DeselectAll();
- tab.contentItem.remove();
+ UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab");
});
}
}
@@ -277,7 +277,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
private onActiveContentItemChanged(contentItem: any) {
if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) {
this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab;
- (CollectionDockingView.Instance as any)._goldenLayout?.isInitialised && CollectionDockingView.Instance.stateChanged();
!this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
}
}