Skip to content

Commit

Permalink
Merge pull request #267 from LoganDark/custom-drop-invalidate
Browse files Browse the repository at this point in the history
Allow custom drops to know when they are invalidated
  • Loading branch information
nealus authored Oct 10, 2021
2 parents c9ff01c + 42e39dd commit 0fde60a
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 9 deletions.
57 changes: 54 additions & 3 deletions examples/demo/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import * as ReactDOM from "react-dom";
import { v4 } from "uuid";
import * as FlexLayout from "../../src/index";
import { Action, Actions, BorderNode, DropInfo, IJsonTabNode, Node, TabNode, TabSetNode } from "../../src/index";
import { ILayoutProps, ITabRenderValues, ITabSetRenderValues } from "../../src/view/Layout";
Expand Down Expand Up @@ -341,9 +342,9 @@ class App extends React.Component<any, { layoutFile: string | null, model: FlexL
onRenderTabSet={this.onRenderTabSet}
onExternalDrag={this.onExternalDrag}
realtimeResize={this.state.realtimeResize}
onTabDrag={(dragging, over, x, y, location) => {
onTabDrag={(dragging, over, x, y, location, refresh) => {
const tabStorageImpl = over.getExtraData().tabStorage_onTabDrag as ILayoutProps['onTabDrag']
if (tabStorageImpl) return tabStorageImpl(dragging, over, x, y, location)
if (tabStorageImpl) return tabStorageImpl(dragging, over, x, y, location, refresh)
return undefined
}}
// classNameMapper={
Expand Down Expand Up @@ -497,9 +498,42 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout }
const refs = useRef<Map<string, HTMLDivElement | undefined>>(new Map()).current
const [emptyElem, setEmptyElem] = useState<HTMLDivElement | null>(null)

// true = down, false = up, null = none
const [scrollDown, setScrollDown] = useState<boolean | null>(null)

const scrollInvalidateRef = useRef<() => void>()
const scroller = useCallback((isDown: boolean) => {
contents?.scrollBy(0, isDown ? 10 : -10)
scrollInvalidateRef.current?.()
}, [contents])

const scrollerRef = useRef(scroller)
scrollerRef.current = scroller

useEffect(() => {
if (scrollDown !== null) {
let scrollInterval: NodeJS.Timeout
let scrollTimeout = setTimeout(() => {
scrollerRef.current(scrollDown)
scrollInterval = setInterval(() => scrollerRef.current(scrollDown), 50)
}, 500)

return () => {
clearTimeout(scrollTimeout)
clearInterval(scrollInterval)
}
}

return
}, [scrollDown])

const kickstartingCallback = useCallback((dragging: TabNode | IJsonTabNode) => {
const json = dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : dragging

if (json.id === undefined) {
json.id = `#${v4()}`
}

setStoredTabs(tabs => [...tabs, json])

if (dragging instanceof TabNode) {
Expand Down Expand Up @@ -537,6 +571,10 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout }
const { insertionIndex } = calculateInsertion(absoluteY)
const json = dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : dragging

if (json.id === undefined) {
json.id = `#${v4()}`
}

setStoredTabs(tabs => {
const newTabs = [...tabs]
const foundAt = newTabs.indexOf(json)
Expand All @@ -551,12 +589,14 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout }
return newTabs
})

setScrollDown(null)

if (dragging instanceof TabNode) {
tab.getModel().doAction(Actions.deleteTab(dragging.getId()));
}
}, [calculateInsertion, tab, layout])

tab.getExtraData().tabStorage_onTabDrag = useCallback(((dragging, over, x, y) => {
tab.getExtraData().tabStorage_onTabDrag = useCallback(((dragging, over, x, y, _, refresh) => {
if (contents && list) {
const layoutDomRect = layout.getDomRect()
const tabRect = tab.getRect()
Expand All @@ -582,12 +622,23 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout }
} else {
const insertion = calculateInsertion(absY)

scrollInvalidateRef.current = refresh

if (absY - rootY < tabRect.height / 5) {
setScrollDown(false)
} else if (absY - rootY > tabRect.height * 4/5) {
setScrollDown(true)
} else {
setScrollDown(null)
}

return {
x: listBounds.left - rootX,
y: insertion.split - rootY - 2, // -2 needed for border thickness, TODO: have flexlayout automatically make this unnecessary for 0-height/width borders
width: listBounds.width,
height: 0,
callback: insertionCallback,
invalidated: () => setScrollDown(null),
cursor: 'row-resize'
}
}
Expand Down
46 changes: 40 additions & 6 deletions src/view/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { FloatingWindowTab } from "./FloatingWindowTab";
import { TabFloating } from "./TabFloating";
import { IJsonTabNode } from "../model/IJsonModel";

export type CustomDragCallback = (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => void;

export interface ILayoutProps {
model: Model;
factory: (node: TabNode) => React.ReactNode;
Expand Down Expand Up @@ -54,12 +56,14 @@ export interface ILayoutProps {
supportsPopout?: boolean | undefined;
popoutURL?: string | undefined;
realtimeResize?: boolean | undefined;
onTabDrag?: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => undefined | {
onTabDrag?: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation, refresh: () => void) => undefined | {
x: number,
y: number,
width: number,
height: number,
callback: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => void,
callback: CustomDragCallback,
// Called once when `callback` is not going to be called anymore (user canceled the drag, moved mouse and you returned a different callback, etc)
invalidated?: () => void,
cursor?: string | undefined
};
}
Expand Down Expand Up @@ -108,7 +112,8 @@ export interface IIcons {

export interface ICustomDropDestination {
rect: Rect;
callback: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => void;
callback: CustomDragCallback;
invalidated: (() => void) | undefined;
dragging: TabNode | IJsonTabNode;
over: TabNode;
x: number;
Expand Down Expand Up @@ -390,8 +395,8 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
}

/** @hidden @internal */
onTabDrag(dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) {
return this.props.onTabDrag?.(dragging, over, x, y, location);
onTabDrag(...args: Parameters<Required<ILayoutProps>['onTabDrag']>) {
return this.props.onTabDrag?.(...args);
}

/** @hidden @internal */
Expand Down Expand Up @@ -691,8 +696,16 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
this.fnNewNodeDropped();
this.fnNewNodeDropped = undefined;
}

try {
this.customDrop?.invalidated?.()
} catch (e) {
console.error(e)
}

DragDrop.instance.hideGlass();
this.newTabJson = undefined;
this.customDrop = undefined;
};

/** @hidden @internal */
Expand All @@ -714,8 +727,16 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
this.fnNewNodeDropped();
this.fnNewNodeDropped = undefined;
}

try {
this.customDrop?.invalidated?.()
} catch (e) {
console.error(e)
}

DragDrop.instance.hideGlass();
this.newTabJson = undefined;
this.customDrop = undefined;
}
};

Expand Down Expand Up @@ -788,6 +809,8 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {

let dropInfo = this.props.model._findDropTargetNode(this.dragNode!, pos.x, pos.y);
if (dropInfo) {
let invalidated = this.customDrop?.invalidated;
const currentCallback = this.customDrop?.callback;
this.customDrop = undefined;

const dragging = this.newTabJson || (this.dragNode instanceof TabNode ? this.dragNode : undefined);
Expand All @@ -799,12 +822,13 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
let customDrop: ICustomDropDestination | undefined = undefined;

try {
const dest = this.onTabDrag(dragging, selected, pos.x - tabRect.x, pos.y - tabRect.y, dropInfo.location);
const dest = this.onTabDrag(dragging, selected, pos.x - tabRect.x, pos.y - tabRect.y, dropInfo.location, () => this.onDragMove(event));

if (dest) {
customDrop = {
rect: new Rect(dest.x + tabRect.x, dest.y + tabRect.y, dest.width, dest.height),
callback: dest.callback,
invalidated: dest.invalidated,
dragging: dragging,
over: selected,
x: pos.x - tabRect.x,
Expand All @@ -817,6 +841,10 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
console.error(e)
}

if (customDrop?.callback == currentCallback) {
invalidated = undefined;
}

this.customDrop = customDrop;
}
}
Expand All @@ -832,6 +860,12 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {

DragDrop.instance.setGlassCursorOverride(this.customDrop?.cursor);
this.outlineDiv!.style.visibility = "visible";

try {
invalidated?.();
} catch (e) {
console.error(e);
}
}
};

Expand Down

0 comments on commit 0fde60a

Please sign in to comment.