From 0816f2d3edc414fc3cb542f74f4e5bfb0da68730 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Sun, 10 Oct 2021 00:44:12 -0700 Subject: [PATCH 1/9] Allow custom drops to know when they are invalidated --- examples/demo/App.tsx | 40 +++++++++++++++++++++++++++++++++++++- src/view/Layout.tsx | 45 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/examples/demo/App.tsx b/examples/demo/App.tsx index c3c90d42..9339b981 100755 --- a/examples/demo/App.tsx +++ b/examples/demo/App.tsx @@ -497,6 +497,33 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout } const refs = useRef>(new Map()).current const [emptyElem, setEmptyElem] = useState(null) + // true = down, false = up, null = none + const [scrollDown, setScrollDown] = useState(null) + + const scroller = useCallback((isDown: boolean) => { + contents?.scrollBy(0, isDown ? 10 : -10) + }, [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 @@ -551,6 +578,8 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout } return newTabs }) + setScrollDown(null) + if (dragging instanceof TabNode) { tab.getModel().doAction(Actions.deleteTab(dragging.getId())); } @@ -581,12 +610,21 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout } } else { const insertion = calculateInsertion(absY) + 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 + callback: insertionCallback, + invalidated: () => setScrollDown(null) } } } diff --git a/src/view/Layout.tsx b/src/view/Layout.tsx index 833f9d4c..8830dd74 100755 --- a/src/view/Layout.tsx +++ b/src/view/Layout.tsx @@ -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; @@ -59,7 +61,9 @@ export interface ILayoutProps { 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 }; } export interface IFontValues { @@ -107,7 +111,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; @@ -388,8 +393,8 @@ export class Layout extends React.Component { } /** @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['onTabDrag']>) { + return this.props.onTabDrag?.(...args); } /** @hidden @internal */ @@ -689,8 +694,16 @@ export class Layout extends React.Component { 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 */ @@ -712,8 +725,16 @@ export class Layout extends React.Component { this.fnNewNodeDropped(); this.fnNewNodeDropped = undefined; } + + try { + this.customDrop?.invalidated?.() + } catch (e) { + console.error(e) + } + DragDrop.instance.hideGlass(); this.newTabJson = undefined; + this.customDrop = undefined; } }; @@ -786,6 +807,8 @@ export class Layout extends React.Component { 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); @@ -795,14 +818,16 @@ export class Layout extends React.Component { if (selected && tabRect?.contains(pos.x, pos.y)) { let customDrop: ICustomDropDestination | undefined = undefined; + const args: Parameters = [dragging, selected, pos.x - tabRect.x, pos.y - tabRect.y, dropInfo.location]; try { - const dest = this.onTabDrag(dragging, selected, pos.x - tabRect.x, pos.y - tabRect.y, dropInfo.location); + const dest = this.onTabDrag(...args); 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, @@ -814,6 +839,10 @@ export class Layout extends React.Component { console.error(e) } + if (customDrop?.callback == currentCallback) { + invalidated = undefined; + } + this.customDrop = customDrop; } } @@ -828,6 +857,12 @@ export class Layout extends React.Component { } this.outlineDiv!.style.visibility = "visible"; + + try { + invalidated?.(); + } catch (e) { + console.error(e); + } } }; From 8e21d4de7f2f0db882008209ca0749c7c3ce21f3 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Sun, 10 Oct 2021 00:53:54 -0700 Subject: [PATCH 2/9] Allow refreshing in custom drag --- examples/demo/App.tsx | 10 +++++++--- src/view/Layout.tsx | 5 ++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/demo/App.tsx b/examples/demo/App.tsx index 9339b981..c5857350 100755 --- a/examples/demo/App.tsx +++ b/examples/demo/App.tsx @@ -341,9 +341,9 @@ class App extends React.Component { + 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={ @@ -500,8 +500,10 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout } // true = down, false = up, null = none const [scrollDown, setScrollDown] = useState(null) + const scrollInvalidateRef = useRef<() => void>() const scroller = useCallback((isDown: boolean) => { contents?.scrollBy(0, isDown ? 10 : -10) + scrollInvalidateRef.current?.() }, [contents]) const scrollerRef = useRef(scroller) @@ -585,7 +587,7 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout } } }, [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() @@ -610,6 +612,8 @@ 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) { diff --git a/src/view/Layout.tsx b/src/view/Layout.tsx index 8830dd74..9f9da9d0 100755 --- a/src/view/Layout.tsx +++ b/src/view/Layout.tsx @@ -56,7 +56,7 @@ 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, @@ -818,10 +818,9 @@ export class Layout extends React.Component { if (selected && tabRect?.contains(pos.x, pos.y)) { let customDrop: ICustomDropDestination | undefined = undefined; - const args: Parameters = [dragging, selected, pos.x - tabRect.x, pos.y - tabRect.y, dropInfo.location]; try { - const dest = this.onTabDrag(...args); + const dest = this.onTabDrag(dragging, selected, pos.x - tabRect.x, pos.y - tabRect.y, dropInfo.location, () => this.onDragMove(event)); if (dest) { customDrop = { From 40a19ac02a2ed3cf5fe00fc8e661de9a58c100e4 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Sun, 10 Oct 2021 08:27:44 -0700 Subject: [PATCH 3/9] invalidated deserves to be with callback :) --- src/view/Layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/view/Layout.tsx b/src/view/Layout.tsx index 7485e13b..be715f46 100755 --- a/src/view/Layout.tsx +++ b/src/view/Layout.tsx @@ -62,9 +62,9 @@ export interface ILayoutProps { width: number, height: number, callback: CustomDragCallback, - cursor?: string | undefined, // 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 + invalidated?: () => void, + cursor?: string | undefined }; } export interface IFontValues { From b6ed36a532daca2c5258692d1bd9e77833b146a3 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Sun, 10 Oct 2021 08:32:50 -0700 Subject: [PATCH 4/9] Update GitHub interface (hopefully) From f4d4b9a523213db0c971b6ca091f596ac190885b Mon Sep 17 00:00:00 2001 From: LoganDark Date: Sun, 10 Oct 2021 08:46:27 -0700 Subject: [PATCH 5/9] fix tabs without ids --- examples/demo/App.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/demo/App.tsx b/examples/demo/App.tsx index fe3fe0bf..388fc523 100755 --- a/examples/demo/App.tsx +++ b/examples/demo/App.tsx @@ -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"; @@ -527,7 +528,7 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout } }, [scrollDown]) const kickstartingCallback = useCallback((dragging: TabNode | IJsonTabNode) => { - const json = dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : dragging + const json = dragging instanceof TabNode ? { id: '#' + v4(), ...dragging.toJson() } as IJsonTabNode : dragging setStoredTabs(tabs => [...tabs, json]) From 73b5a839d58bc1123c478a5ea097fae68ac5a9c2 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Sun, 10 Oct 2021 08:47:11 -0700 Subject: [PATCH 6/9] whoops --- examples/demo/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/demo/App.tsx b/examples/demo/App.tsx index 388fc523..ecb18982 100755 --- a/examples/demo/App.tsx +++ b/examples/demo/App.tsx @@ -528,7 +528,7 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout } }, [scrollDown]) const kickstartingCallback = useCallback((dragging: TabNode | IJsonTabNode) => { - const json = dragging instanceof TabNode ? { id: '#' + v4(), ...dragging.toJson() } as IJsonTabNode : dragging + const json = { id: '#' + v4(), ...(dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : dragging) } setStoredTabs(tabs => [...tabs, json]) From f55a482ff1b3e32905d878e9e69c2785eaa4bdcf Mon Sep 17 00:00:00 2001 From: LoganDark Date: Sun, 10 Oct 2021 09:06:31 -0700 Subject: [PATCH 7/9] this probably makes the most sense... --- examples/demo/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/demo/App.tsx b/examples/demo/App.tsx index ecb18982..8ab05a40 100755 --- a/examples/demo/App.tsx +++ b/examples/demo/App.tsx @@ -528,7 +528,7 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout } }, [scrollDown]) const kickstartingCallback = useCallback((dragging: TabNode | IJsonTabNode) => { - const json = { id: '#' + v4(), ...(dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : dragging) } + const json = dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : { id: '#' + v4(), ...dragging } setStoredTabs(tabs => [...tabs, json]) From 9168c0cbcc734d6e49da23935b6be3a9ba696d6f Mon Sep 17 00:00:00 2001 From: LoganDark Date: Sun, 10 Oct 2021 09:23:04 -0700 Subject: [PATCH 8/9] propagate fix to other callback (whoops) --- examples/demo/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/demo/App.tsx b/examples/demo/App.tsx index 8ab05a40..4fb197ad 100755 --- a/examples/demo/App.tsx +++ b/examples/demo/App.tsx @@ -565,7 +565,7 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout } const insertionCallback = useCallback((dragging: TabNode | IJsonTabNode, _, __, y: number) => { const absoluteY = y + tab.getRect().y + layout.getDomRect().top const { insertionIndex } = calculateInsertion(absoluteY) - const json = dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : dragging + const json = dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : { id: '#' + v4(), ...dragging } setStoredTabs(tabs => { const newTabs = [...tabs] From 42e39dde082a5194e53b5d5b4a777507b9a309b6 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Sun, 10 Oct 2021 09:25:49 -0700 Subject: [PATCH 9/9] avoid creating new object when id is not present --- examples/demo/App.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/demo/App.tsx b/examples/demo/App.tsx index 4fb197ad..f218cdee 100755 --- a/examples/demo/App.tsx +++ b/examples/demo/App.tsx @@ -528,7 +528,11 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout } }, [scrollDown]) const kickstartingCallback = useCallback((dragging: TabNode | IJsonTabNode) => { - const json = dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : { id: '#' + v4(), ...dragging } + const json = dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : dragging + + if (json.id === undefined) { + json.id = `#${v4()}` + } setStoredTabs(tabs => [...tabs, json]) @@ -565,7 +569,11 @@ function TabStorage({ tab, layout }: { tab: TabNode, layout: FlexLayout.Layout } const insertionCallback = useCallback((dragging: TabNode | IJsonTabNode, _, __, y: number) => { const absoluteY = y + tab.getRect().y + layout.getDomRect().top const { insertionIndex } = calculateInsertion(absoluteY) - const json = dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : { id: '#' + v4(), ...dragging } + const json = dragging instanceof TabNode ? dragging.toJson() as IJsonTabNode : dragging + + if (json.id === undefined) { + json.id = `#${v4()}` + } setStoredTabs(tabs => { const newTabs = [...tabs]