Skip to content

Commit

Permalink
more UI tweaks and additional controls for active callbacks page
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Aug 8, 2024
1 parent 271e423 commit dca9444
Show file tree
Hide file tree
Showing 30 changed files with 964 additions and 289 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.3.0-rc18] - 2024-08-08

### Changed

- fixed a bug where updating timestamp of linked agents wouldn't unhide it

## [3.3.0-rc17] - 2024-08-07

### Changed
Expand Down
9 changes: 9 additions & 0 deletions MythicReactUI/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.18] - 2024-08-08

### Changed

- Adjusted the border bounds for a few elements in the UI to minimize blank space
- Added a right-click context menu to the active callbacks table
- Added a sub-menu structure to the active callbacks menu to keep the menu from getting too long
- Added an option to open multiple callback tabs at once

## [0.2.17] - 2024-08-07

### Changed
Expand Down
4 changes: 2 additions & 2 deletions MythicReactUI/src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export function App(props) {
wordBreak: "break-all", flexDirection: "column", justifyContent: "center"}}
pauseOnFocusLoss={false} />
<div style={{ maxHeight: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}>
<div style={{ minHeight: '56px', flexGrow: 0 }}>
<div style={{ minHeight: '50px', flexGrow: 0 }}>
{me.loggedIn && me.user !== undefined && me.user !== null ? (
<TopAppBar me={me} theme={themeMode} toggleTheme={themeToggler} />
) : null}
Expand All @@ -164,7 +164,7 @@ export function App(props) {
onClose={()=>{setOpenRefreshDialog(false);}} />}
/>
}
<div style={{ margin: '0px 5px 5px 5px', flexGrow: 1, flexDirection: 'column', height: "calc(100% - 4rem)", }}>
<div style={{ margin: '0px 0px 0px 0px', flexGrow: 1, flexDirection: 'column', height: "calc(100% - 4rem)", }}>
<Routes>
<Route path='/new/login' element={<LoginForm me={me}/>}/>
<Route path='/new/invite' element={<InviteForm me={me}/>}/>
Expand Down
233 changes: 233 additions & 0 deletions MythicReactUI/src/components/MythicComponents/MythicNestedMenus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import * as React from "react";
import styled from "@emotion/styled";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import ArrowRight from "@mui/icons-material/ArrowRight";
// from https://medium.com/geekculture/creating-a-dropdown-with-nested-menu-items-using-react-mui-bb0c084226da
export const Dropdown = React.forwardRef(
(
{
menu,
isOpen: controlledIsOpen,
onOpen: onControlledOpen,
externallyOpen,
minWidth
},
ref
) => {
const [isInternalOpen, setInternalOpen] = React.useState(null);

const isOpen = controlledIsOpen || isInternalOpen;

let anchorRef = React.useRef(null);
if (ref) {
anchorRef = ref;
}

const handleClose = (event) => {
event.stopPropagation();

if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}

handleForceClose();
};

const handleForceClose = () => {
onControlledOpen ? onControlledOpen(null) : setInternalOpen(null);
};

const renderMenu = (menuItem, index) => {
const { ...props } = menuItem.props;

let extraProps = {};
if (props.menu) {
extraProps = {
parentMenuOpen: isOpen
};
}

return React.createElement(menuItem.type, {
...props,
key: index,
...extraProps,
onClick: (event) => {
event.stopPropagation();
if (menuItem.props.onClick) {
menuItem.props.onClick(event);
}
},
children: props.menu
? React.Children.map(props.menu, renderMenu)
: props.children
});
};

return (
<>

<Menu
PaperProps={{ sx: { minWidth: minWidth ?? 0 } }}
anchorEl={isOpen}
open={externallyOpen}
onClose={handleClose}
>
{React.Children.map(menu, renderMenu)}
</Menu>
</>
);
}
);

const NestedMenuItem = React.forwardRef((props, ref) => {
const {
parentMenuOpen,
label,
rightIcon = <ArrowRight style={{ fontSize: 16 }} />,
children,
customTheme,
className,
tabIndex: tabIndexProp,
ContainerProps: ContainerPropsProp = {},
rightAnchored,
...MenuItemProps
} = props;

const { ref: containerRefProp, ...ContainerProps } = ContainerPropsProp;

const menuItemRef = React.useRef(null);
React.useImperativeHandle(ref, () => menuItemRef.current);

const containerRef = React.useRef(null);
React.useImperativeHandle(containerRefProp, () => containerRef.current);

const menuContainerRef = React.useRef(null);

const [isSubMenuOpen, setIsSubMenuOpen] = React.useState(false);

const handleMouseEnter = (event) => {
setIsSubMenuOpen(true);

if (ContainerProps?.onMouseEnter) {
ContainerProps.onMouseEnter(event);
}
};

const handleMouseLeave = (event) => {
setIsSubMenuOpen(false);

if (ContainerProps?.onMouseLeave) {
ContainerProps.onMouseLeave(event);
}
};

const isSubmenuFocused = () => {
const active = containerRef.current?.ownerDocument?.activeElement;

for (const child of menuContainerRef.current?.children ?? []) {
if (child === active) {
return true;
}
}
return false;
};

const handleFocus = (event) => {
if (event.target === containerRef.current) {
setIsSubMenuOpen(true);
}

if (ContainerProps?.onFocus) {
ContainerProps.onFocus(event);
}
};

const handleKeyDown = (event) => {
if (event.key === "Escape") {
return;
}

if (isSubmenuFocused()) {
event.stopPropagation();
}

const active = containerRef.current?.ownerDocument?.activeElement;

if (event.key === "ArrowLeft" && isSubmenuFocused()) {
containerRef.current?.focus();
}

if (
event.key === "ArrowRight" &&
event.target === containerRef.current &&
event.target === active
) {
const firstChild = menuContainerRef.current?.children[0];
firstChild?.focus();
}
};

const open = isSubMenuOpen && parentMenuOpen;

let tabIndex;
if (!props.disabled) {
tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1;
}

return (
<div
{...ContainerProps}
ref={containerRef}
onFocus={handleFocus}
tabIndex={tabIndex}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onKeyDown={handleKeyDown}
>
<MenuItem
{...MenuItemProps}
data-open={!!open || undefined}
className={className}
ref={menuItemRef}
>
{label}
<div style={{ flexGrow: 1 }} />
{rightIcon}
</MenuItem>
<Menu
hideBackdrop
style={{ pointerEvents: "none" }}
anchorEl={menuItemRef.current}
anchorOrigin={{
vertical: "top",
horizontal: rightAnchored ? "left" : "right"
}}
transformOrigin={{
vertical: "top",
horizontal: rightAnchored ? "right" : "left"
}}
css={customTheme}
open={!!open}
autoFocus={false}
disableAutoFocus
disableEnforceFocus
onClose={() => {
setIsSubMenuOpen(false);
}}
>
<div ref={menuContainerRef} style={{ pointerEvents: "auto" }}>
{children}
</div>
</Menu>
</div>
);
});

export const DropdownMenuItem = styled(MenuItem)`
`;

export const DropdownNestedMenuItem = styled(NestedMenuItem)`
`;
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import MenuList from '@mui/material/MenuList';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import Paper from '@mui/material/Paper';
import {useTheme} from '@mui/material/styles';
import {Dropdown, DropdownMenuItem, DropdownNestedMenuItem} from "../MythicNestedMenus";

const CellPreMemo = ({ VariableSizeGridProps: { style, rowIndex, columnIndex, data } }) => {
const [openContextMenu, setOpenContextMenu] = React.useState(false);
const rowClassName = data.gridUUID + "row" + rowIndex;
const contextMenuOptions = data?.rowContextMenuOptions || [];
const [contextMenuOptions, setContextMenuOptions] = React.useState(data?.rowContextMenuOptions || []);
const dropdownAnchorRef = React.useRef(null);
const handleDoubleClick = useCallback(
(e) => {
Expand Down Expand Up @@ -39,8 +40,8 @@ const CellPreMemo = ({ VariableSizeGridProps: { style, rowIndex, columnIndex, da
}
}
}
const handleMenuItemClick = (event, index) => {
contextMenuOptions[index].click({event, columnIndex, rowIndex, data: data.items[rowIndex][columnIndex]?.props?.rowData || {}});
const handleMenuItemClick = (event, clickOption) => {
clickOption({event, columnIndex, rowIndex, data: data.items[rowIndex][columnIndex]?.props?.rowData || {}});
setOpenContextMenu(false);
};
const handleContextClick = useCallback(
Expand All @@ -49,11 +50,19 @@ const CellPreMemo = ({ VariableSizeGridProps: { style, rowIndex, columnIndex, da
if(item.disableFilterMenu){
return;
}
if(data.onRowContextMenuClick){
const newMenuItems = data.onRowContextMenuClick({rowDataStatic: data.items[rowIndex][columnIndex]?.props?.rowData});
if(newMenuItems.length > 0){
setContextMenuOptions(newMenuItems);
setOpenContextMenu(true);
return;
}
}
if(contextMenuOptions && contextMenuOptions.length > 0){
setOpenContextMenu(true);
}
},
[contextMenuOptions] // eslint-disable-line react-hooks/exhaustive-deps
[contextMenuOptions, data.onRowContextMenuClick] // eslint-disable-line react-hooks/exhaustive-deps
);
return (
<div style={{...style, ...cellStyle, ...rowStyle}}
Expand All @@ -75,8 +84,6 @@ const CellPreMemo = ({ VariableSizeGridProps: { style, rowIndex, columnIndex, da
);
};
const ContextMenu = ({openContextMenu, dropdownAnchorRef, contextMenuOptions, setOpenContextMenu, handleMenuItemClick}) => {
const theme = useTheme();

const handleClose = (event) => {
if (dropdownAnchorRef.current && dropdownAnchorRef.current.contains(event.target)) {
return;
Expand All @@ -86,7 +93,50 @@ const ContextMenu = ({openContextMenu, dropdownAnchorRef, contextMenuOptions, se

return (
openContextMenu &&
<Popper open={openContextMenu} anchorEl={dropdownAnchorRef.current} role={undefined} transition disablePortal style={{zIndex: 4}}>
<ClickAwayListener onClickAway={handleClose} mouseEvent={"onMouseDown"}>
<Dropdown
isOpen={dropdownAnchorRef.current}
onOpen={setOpenContextMenu}
externallyOpen={openContextMenu}
menu={[
contextMenuOptions.map((option, index) => (
option.type === 'item' ? (
<DropdownMenuItem
key={option.name}
disabled={option.disabled}
onClick={(event) => handleMenuItemClick(event, option.click)}
>
{option.icon}{option.name}
</DropdownMenuItem>
) : option.type === 'menu' ? (
<DropdownNestedMenuItem
label={option.name}
disabled={option.disabled}
menu={
option.menuItems.map((menuOption, indx) => (
<DropdownMenuItem
key={menuOption.name}
disabled={menuOption.disabled}
onClick={(event) => handleMenuItemClick(event, menuOption.click)}
>
{menuOption.icon}{menuOption.name}
</DropdownMenuItem>
))
}
/>
) : null
)),
]}
/>
</ClickAwayListener>

)
}
const Cell = React.memo(CellPreMemo);
export default Cell;

/*
<Popper open={openContextMenu} anchorEl={dropdownAnchorRef.current} role={undefined} transition disablePortal style={{zIndex: 4}}>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
Expand All @@ -111,8 +161,4 @@ const ContextMenu = ({openContextMenu, dropdownAnchorRef, contextMenuOptions, se
</Grow>
)}
</Popper>
)
}
const Cell = React.memo(CellPreMemo);
export default Cell;

*/
Loading

0 comments on commit dca9444

Please sign in to comment.