@finos/legend-lego
Version:
Legend code editor support
126 lines • 7.88 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChevronDownIcon, clsx, ContextMenu, DragPreviewLayer, ControlledDropdownMenu, MenuContent, MenuContentItem, PanelEntryDropZonePlaceholder, TimesIcon, useDragPreviewLayer, MenuContentDivider, PushPinIcon, } from '@finos/legend-art';
import { observer } from 'mobx-react-lite';
import { useRef, useCallback } from 'react';
import { useDrop, useDrag } from 'react-dnd';
export const TAB_MANAGER__TAB_TEST_ID = 'tab-manager__tab';
const horizontalToVerticalScroll = (event) => {
// if scrolling is more horizontal than vertical, there's nothing much to do, the OS should handle it just fine
// else, intercept
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) {
return;
}
event.stopPropagation();
let deltaX;
// NOTE: only convert horizontal to vertical scroll when the scroll causes more horizontal than vertical displacement
// let the direction of `deltaY` be the direction of the scroll, i.e.
// - if we scroll upward, that translate to a left scroll
// - if we scroll downward, that translates to a right scroll
if (event.deltaY === 0) {
deltaX = event.deltaY;
}
else if (event.deltaY < 0) {
deltaX = -Math.abs(event.deltaY);
}
else {
deltaX = Math.abs(event.deltaY);
}
event.currentTarget.scrollBy(deltaX, 0);
};
const TabContextMenu = observer((props) => {
const { tabState, managerTabState } = props;
const close = () => managerTabState.closeTab(tabState);
const closeOthers = () => managerTabState.closeAllOtherTabs(tabState);
const closeAll = () => managerTabState.closeAllTabs();
const togglePin = () => {
if (tabState.isPinned) {
managerTabState.unpinTab(tabState);
}
else {
managerTabState.pinTab(tabState);
}
};
return (_jsxs(MenuContent, { children: [_jsx(MenuContentItem, { onClick: close, children: "Close" }), _jsx(MenuContentItem, { disabled: managerTabState.tabs.length < 2, onClick: closeOthers, children: "Close Others" }), _jsx(MenuContentItem, { onClick: closeAll, children: "Close All" }), _jsx(MenuContentDivider, {}), _jsx(MenuContentItem, { onClick: togglePin, children: tabState.isPinned ? 'Unpin' : 'Pin' })] }));
});
const Tab = observer((props) => {
const ref = useRef(null);
const { tabManagerState, tabState, tabRenderer } = props;
// Drag and Drop
const handleHover = useCallback((item, monitor) => {
const draggingTab = item.tab;
const hoveredTab = tabState;
const dragIndex = tabManagerState.tabs.findIndex((e) => e === draggingTab);
const hoverIndex = tabManagerState.tabs.findIndex((e) => e === hoveredTab);
const hoverBoundingReact = ref.current?.getBoundingClientRect();
const distanceThreshold = ((hoverBoundingReact?.left ?? 0) - (hoverBoundingReact?.right ?? 0)) /
2;
const dragDistance = (monitor.getClientOffset()?.x ?? 0) -
(hoverBoundingReact?.right ?? 0);
if (dragIndex < hoverIndex && dragDistance < distanceThreshold) {
return;
}
if (dragIndex > hoverIndex && dragDistance > distanceThreshold) {
return;
}
tabManagerState.swapTabs(draggingTab, hoveredTab);
}, [tabManagerState, tabState]);
const closeTabOnMiddleClick = (currTab) => (event) => {
if (event.nativeEvent.button === 1) {
tabManagerState.closeTab(currTab);
}
};
const [{ isBeingDraggedEditorPanel }, dropConnector] = useDrop(() => ({
accept: [tabManagerState.dndType],
hover: (item, monitor) => handleHover(item, monitor),
collect: (monitor) => ({
isBeingDraggedEditorPanel: monitor.getItem()
?.tab,
}),
}), [handleHover]);
const isBeingDragged = tabState === isBeingDraggedEditorPanel;
const [, dragConnector, dragPreviewConnector] = useDrag(() => ({
type: tabManagerState.dndType,
item: () => ({
tab: tabState,
}),
}), [tabState, tabManagerState]);
dragConnector(dropConnector(ref));
useDragPreviewLayer(dragPreviewConnector);
return (_jsx("div", { ref: ref, "data-testid": TAB_MANAGER__TAB_TEST_ID, className: clsx('tab-manager__tab', {
'tab-manager__tab--active': tabState === tabManagerState.currentTab,
'tab-manager__tab--dragged': isBeingDragged,
}), onMouseUp: closeTabOnMiddleClick(tabState), children: _jsx(PanelEntryDropZonePlaceholder, { isDragOver: false, className: "tab-manager__tab__dnd__placeholder", children: _jsxs(ContextMenu, { content: _jsx(TabContextMenu, { tabState: tabState, managerTabState: tabManagerState }), className: "tab-manager__tab__content", children: [_jsx("button", { className: "tab-manager__tab__label", tabIndex: -1, onClick: () => tabManagerState.openTab(tabState), title: tabState.description, children: tabRenderer?.(tabState) ?? tabState.label }), tabState.isPinned && (_jsx("button", { className: "tab-manager__tab__pin-btn", onClick: () => tabManagerState.unpinTab(tabState), tabIndex: -1, title: "Unpin", children: _jsx(PushPinIcon, {}) })), !tabState.isPinned && (_jsx("button", { className: "tab-manager__tab__close-btn", onClick: () => tabManagerState.closeTab(tabState), tabIndex: -1, title: "Close", children: _jsx(TimesIcon, {}) }))] }) }) }));
});
const TabMenu = observer((props) => {
const { managerTabState } = props;
return (_jsx(ControlledDropdownMenu, { className: "tab-manager__menu__toggler", title: "Show All Tabs", content: _jsx(MenuContent, { className: "tab-manager__menu", children: managerTabState.tabs.map((tabState) => (_jsxs(MenuContentItem, { className: clsx('tab-manager__menu__item', {
'tab-manager__menu__item--active': tabState === managerTabState.currentTab,
}), onClick: () => managerTabState.openTab(tabState), children: [_jsx("div", { className: "tab-manager__menu__item__label", children: tabState.label }), _jsx("div", { className: "tab-manager__menu__item__close-btn", onClick: (event) => {
// NOTE: prevent default action of dropdown menu
event.stopPropagation();
managerTabState.closeTab(tabState);
}, tabIndex: -1, title: "Close", children: _jsx(TimesIcon, {}) })] }, tabState.uuid))) }), menuProps: {
anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
transformOrigin: { vertical: 'top', horizontal: 'right' },
}, children: _jsx(ChevronDownIcon, {}) }));
});
export const TabManager = observer((props) => {
const { tabManagerState, tabRenderer } = props;
return (_jsxs("div", { className: "tab-manager", children: [_jsxs("div", { className: "tab-manager__content", onWheel: horizontalToVerticalScroll, children: [tabManagerState.tabs.map((tab) => (_jsx(Tab, { tabState: tab, tabManagerState: tabManagerState, tabRenderer: tabRenderer }, tab.uuid))), _jsx(DragPreviewLayer, { labelGetter: (item) => item.tab.label, types: [tabManagerState.dndType] })] }), _jsx(TabMenu, { managerTabState: tabManagerState })] }));
});
//# sourceMappingURL=TabManager.js.map