Something went wrong. Try again.
Starship Simulator Controls thoriumsim.com
Something went wrong. Try again.
8.9 kB · 309 lines
TSX
at develop
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310import * as React from "react";
import type { BorderNode } from "../model/BorderNode";import type { TabNode } from "../model/TabNode";import type { TabSetNode } from "../model/TabSetNode";import { Orientation } from "../Orientation";import type { Rect } from "../Rect";import type { LayoutInternal } from "./Layout";import { startDrag } from "./Utils";
/** @internal */export const useTabOverflow = ( layout: LayoutInternal, node: TabSetNode | BorderNode, orientation: Orientation, tabStripRef: React.RefObject<HTMLElement | null>, miniScrollRef: React.RefObject<HTMLElement | null>, tabClassName: string,) => { const [hiddenTabs, setHiddenTabs] = React.useState<number[]>([]); const [isShowHiddenTabs, setShowHiddenTabs] = React.useState<boolean>(false); const [isDockStickyButtons, setDockStickyButtons] = React.useState<boolean>(false);
const selfRef = React.useRef<HTMLDivElement | null>(null); const userControlledPositionRef = React.useRef<boolean>(false); const updateHiddenTabsTimerRef = React.useRef<ReturnType<typeof setTimeout> | undefined>( undefined, ); const hiddenTabsRef = React.useRef<number[]>([]); const thumbInternalPos = React.useRef<number>(0); const repositioningRef = React.useRef<boolean>(false); hiddenTabsRef.current = hiddenTabs;
// if node id changes (new model) then reset scroll to 0 React.useLayoutEffect(() => { if (tabStripRef.current) { setScrollPosition(0); } }, [node.getId()]);
// if selected node or tabset/border rectangle change then unset usercontrolled (so selected tab will be kept in view) React.useLayoutEffect(() => { userControlledPositionRef.current = false; }, [node.getSelectedNode(), node.getRect().width, node.getRect().height]);
React.useLayoutEffect(() => { checkForOverflow(); // if tabs + sticky buttons length > scroll area => move sticky buttons to right buttons
if (userControlledPositionRef.current === false) { scrollIntoView(); }
updateScrollMetrics(); updateHiddenTabs(); });
function scrollIntoView() { const selectedTabNode = node.getSelectedNode() as TabNode; if (selectedTabNode && tabStripRef.current) { const stripRect = layout.getBoundingClientRect(tabStripRef.current); const selectedRect = selectedTabNode.getTabRect()!;
let shift = getNear(stripRect) - getNear(selectedRect); if (shift > 0 || getSize(selectedRect) > getSize(stripRect)) { setScrollPosition(getScrollPosition(tabStripRef.current) - shift); repositioningRef.current = true; // prevent onScroll setting userControlledPosition } else { shift = getFar(selectedRect) - getFar(stripRect); if (shift > 0) { setScrollPosition(getScrollPosition(tabStripRef.current) + shift); repositioningRef.current = true; } } } }
const updateScrollMetrics = () => { if (tabStripRef.current && miniScrollRef.current) { const t = tabStripRef.current; const s = miniScrollRef.current;
const size = getElementSize(t); const scrollSize = getScrollSize(t); const position = getScrollPosition(t);
if (scrollSize > size && scrollSize > 0) { let thumbSize = (size * size) / scrollSize; let adjust = 0; if (thumbSize < 20) { adjust = 20 - thumbSize; thumbSize = 20; } const thumbPos = (position * (size - adjust)) / scrollSize; if (orientation === Orientation.HORZ) { s.style.width = `${thumbSize}px`; s.style.left = `${thumbPos}px`; } else { s.style.height = `${thumbSize}px`; s.style.top = `${thumbPos}px`; } s.style.display = "block"; } else { s.style.display = "none"; }
if (orientation === Orientation.HORZ) { s.style.bottom = "0px"; } else { s.style.right = "0px"; } } };
const updateHiddenTabs = () => { const newHiddenTabs = findHiddenTabs(); const showHidden = newHiddenTabs.length > 0;
if (showHidden !== isShowHiddenTabs) { setShowHiddenTabs(showHidden); }
if (updateHiddenTabsTimerRef.current === undefined) { // throttle updates to prevent Maximum update depth exceeded error updateHiddenTabsTimerRef.current = setTimeout(() => { const newHiddenTabs = findHiddenTabs(); if (!arraysEqual(newHiddenTabs, hiddenTabsRef.current)) { setHiddenTabs(newHiddenTabs); }
updateHiddenTabsTimerRef.current = undefined; }, 100); } };
const onScroll = () => { if (!repositioningRef.current) { userControlledPositionRef.current = true; } repositioningRef.current = false; updateScrollMetrics(); updateHiddenTabs(); };
const onScrollPointerDown = (event: React.PointerEvent<HTMLElement>) => { event.stopPropagation(); miniScrollRef.current!.setPointerCapture(event.pointerId); const r = miniScrollRef.current?.getBoundingClientRect()!; if (orientation === Orientation.HORZ) { thumbInternalPos.current = event.clientX - r.x; } else { thumbInternalPos.current = event.clientY - r.y; } startDrag(event.currentTarget.ownerDocument, event, onDragMove, onDragEnd, onDragCancel); };
const onDragMove = (x: number, y: number) => { if (tabStripRef.current && miniScrollRef.current) { const t = tabStripRef.current; const s = miniScrollRef.current; const size = getElementSize(t); const scrollSize = getScrollSize(t); const thumbSize = getElementSize(s);
const r = t.getBoundingClientRect()!; let thumb = 0; if (orientation === Orientation.HORZ) { thumb = x - r.x - thumbInternalPos.current; } else { thumb = y - r.y - thumbInternalPos.current; }
thumb = Math.max(0, Math.min(scrollSize - thumbSize, thumb)); if (size > 0) { const scrollPos = (thumb * scrollSize) / size; setScrollPosition(scrollPos); } } };
const onDragEnd = () => {};
const onDragCancel = () => {};
const checkForOverflow = () => { if (tabStripRef.current) { const strip = tabStripRef.current; const tabContainer = strip.firstElementChild!;
const offset = isDockStickyButtons ? 10 : 0; // prevents flashing, after sticky buttons docked set, must be 10 pixels smaller before unsetting const dock = getElementSize(tabContainer) + offset > getElementSize(tabStripRef.current); if (dock !== isDockStickyButtons) { setDockStickyButtons(dock); } } };
const findHiddenTabs: () => number[] = () => { const hidden: number[] = []; if (tabStripRef.current) { const strip = tabStripRef.current; const stripRect = strip.getBoundingClientRect(); const visibleNear = getNear(stripRect) - 1; const visibleFar = getFar(stripRect) + 1;
const tabContainer = strip.firstElementChild!;
let i = 0; Array.from(tabContainer.children).forEach((child) => { const tabRect = child.getBoundingClientRect();
if (child.classList.contains(tabClassName)) { if (getNear(tabRect) < visibleNear || getFar(tabRect) > visibleFar) { hidden.push(i); } i++; } }); }
return hidden; };
const onMouseWheel = (event: React.WheelEvent<HTMLElement>) => { if (tabStripRef.current) { if (node.getChildren().length === 0) return;
let delta = 0; if (Math.abs(event.deltaY) > 0) { delta = -event.deltaY; if (event.deltaMode === 1) { // DOM_DELTA_LINE 0x01 The delta values are specified in lines. delta *= 40; } const newPos = getScrollPosition(tabStripRef.current) - delta; const maxScroll = getScrollSize(tabStripRef.current) - getElementSize(tabStripRef.current); const p = Math.max(0, Math.min(maxScroll, newPos)); setScrollPosition(p); event.stopPropagation(); } } };
// orientation helpers:
const getNear = (rect: DOMRect | Rect) => { if (orientation === Orientation.HORZ) { return rect.x; } return rect.y; };
const getFar = (rect: DOMRect | Rect) => { if (orientation === Orientation.HORZ) { return rect.right; } return rect.bottom; };
const getElementSize = (elm: Element) => { if (orientation === Orientation.HORZ) { return elm.clientWidth; } return elm.clientHeight; };
const getSize = (rect: DOMRect | Rect) => { if (orientation === Orientation.HORZ) { return rect.width; } return rect.height; };
const getScrollSize = (elm: Element) => { if (orientation === Orientation.HORZ) { return elm.scrollWidth; } return elm.scrollHeight; };
const setScrollPosition = (p: number) => { if (orientation === Orientation.HORZ) { tabStripRef.current!.scrollLeft = p; } else { tabStripRef.current!.scrollTop = p; } };
const getScrollPosition = (elm: Element) => { if (orientation === Orientation.HORZ) { return elm.scrollLeft; } return elm.scrollTop; };
return { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isDockStickyButtons, isShowHiddenTabs, };};
function arraysEqual(arr1: number[], arr2: number[]) { return arr1.length === arr2.length && arr1.every((val, index) => val === arr2[index]);}