add dropdown submenu

This commit is contained in:
zokutyou2@gmail.com 2021-06-13 21:10:43 +09:00
parent 6824666424
commit fffd0017d8
9 changed files with 185 additions and 105 deletions

View file

@ -7,14 +7,17 @@
"start": "gulp"
},
"dependencies": {
"@popperjs/core": "^2.9.2",
"bootstrap": "^3.3.7",
"classnames": "^2.3.1",
"lodash": "^4.17.21",
"mock-xmlhttprequest": "^1.1.0",
"popper.js": "^1.16.1",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-codemirror": "^1.0.0",
"react-dom": "^17.0.2",
"react-popper": "^2.2.5",
"react-redux": "^7.2.4",
"react-test-renderer": "^17.0.2",
"redux": "^4.1.0",

View file

@ -190,4 +190,52 @@
transform: translate(-1px, 2px);
}
.dropdown-submenu {
position:relative;
}
.dropdown-submenu>.dropdown-menu {
top:0;
left:100%;
margin-top:-6px;
margin-left:-1px;
-webkit-border-radius:0 6px 6px 6px;
-moz-border-radius:0 6px 6px 6px;
border-radius:0 6px 6px 6px;
}
.dropdown-submenu:hover>.dropdown-menu {
display:block;
}
.dropdown-submenu>a:after {
display:block;
content:" ";
float:right;
width:0;
height:0;
border-color:transparent;
border-style:solid;
border-width:5px 0 5px 5px;
border-left-color:#cccccc;
margin-top:5px;
margin-right:-10px;
}
.dropdown-submenu:hover>a:after {
border-left-color:#ffffff;
}
.dropdown-submenu.pull-left {
float:none;
}
.dropdown-submenu.pull-left>.dropdown-menu {
left:-100%;
margin-left:10px;
-webkit-border-radius:6px 0 6px 6px;
-moz-border-radius:6px 0 6px 6px;
border-radius:6px 0 6px 6px;
}
}

View file

@ -102,15 +102,7 @@ exports[`Flowcolumns Components should render QuickActionsColumn 1`] = `
<td
className="col-quickactions"
>
<div>
<div
className="quickaction"
>
<i
className="fa fa-fw fa-ellipsis-h"
/>
</div>
</div>
<div />
</td>
`;

View file

@ -51,72 +51,7 @@ exports[`FlowRow Component should render correctly 1`] = `
<td
className="col-quickactions"
>
<div>
<div
className="quickaction"
>
<i
className="fa fa-fw fa-ellipsis-h"
/>
</div>
</div>
<div />
</td>
<div
className="dropdown pull-left btn btn-default"
>
<a
className="special"
href="#"
onClick={[Function]}
>
Actions
</a>
<ul
className="dropdown-menu"
role="menu"
>
<li>
<a
href="#"
onClick={[Function]}
>
<i
className="fa fa-fw fa-plus"
/>
 Intercept
address
</a>
</li>
<li>
<a
href="#"
onClick={[Function]}
>
<i
className="fa fa-fw fa-plus"
/>
 Intercept
address/path
</a>
</li>
<li>
<a
href="#"
onClick={[Function]}
>
<i
className="fa fa-fw fa-plus"
/>
 Intercept all POST requests from this host
</a>
</li>
</ul>
</div>
</tr>
`;

View file

@ -4,6 +4,7 @@ import classnames from 'classnames'
import {RequestUtils, ResponseUtils} from '../../flow/utils.js'
import {formatSize, formatTimeDelta, formatTimeStamp} from '../../utils.js'
import * as flowActions from "../../ducks/flows";
import HoverMenu from "./HoverMenu";
export const defaultColumnNames = ["tls", "icon", "path", "method", "status", "size", "time"]
@ -163,7 +164,7 @@ export function TimeStampColumn({flow}) {
TimeStampColumn.headerClass = 'col-timestamp'
TimeStampColumn.headerName = 'TimeStamp'
export function QuickActionsColumn({flow}) {
export function QuickActionsColumn({flow, selected}) {
const dispatch = useDispatch()
function resume(e) {
@ -175,7 +176,9 @@ export function QuickActionsColumn({flow}) {
return (
<td className="col-quickactions">
<div>
<div className="quickaction"><i className="fa fa-fw fa-ellipsis-h"/></div>
{selected ?
<div className="quickaction"><HoverMenu /></div>
: null}
{flow.intercepted
? <div className="quickaction" onClick={resume}><i className="fa fa-fw fa-play text-success"/></div>
: null}

View file

@ -4,7 +4,6 @@ import classnames from 'classnames'
import {defaultColumnNames} from './FlowColumns'
import { pure } from '../../utils'
import {getDisplayColumns} from './FlowTableHead'
import HoverMenu from './HoverMenu'
import { connect } from 'react-redux'
FlowRow.propTypes = {
@ -28,9 +27,8 @@ function FlowRow({ flow, selected, highlighted, onSelect, displayColumnNames })
return (
<tr className={className} onClick={() => onSelect(flow.id)}>
{displayColumns.map(Column => (
<Column key={Column.name} flow={flow}/>
<Column key={Column.name} flow={flow} selected={selected}/>
))}
<HoverMenu/>
</tr>
)
}

View file

@ -1,16 +1,12 @@
import React, { Component } from 'react'
import React, { Component, useCallback } from 'react'
import { connect } from 'react-redux'
import { update as updateSettings } from "../../ducks/settings"
import Dropdown from '../common/Dropdown'
import DropdownSubMenu from '../common/DropdownSubMenu'
class HoverMenu extends Component {
constructor(props, context) {
super(props, context)
}
onClick(e, example) {
let SubMenu = ({flow, intercept, updateSettings}) => {
const onClick = useCallback((e, example) => {
e.preventDefault();
let intercept = this.props.intercept
if (intercept && intercept.includes(example)) {
return
}
@ -19,7 +15,46 @@ class HoverMenu extends Component {
} else {
intercept = `${intercept} | ${example}`
}
this.props.updateSettings({ intercept })
updateSettings({ intercept })
}, [intercept])
return (
<DropdownSubMenu text="Intercept requests like this">
<a href="#" onClick={(e) =>{
onClick(e, flow.request.host)
}}>
<i className="fa fa-fw fa-plus"></i>
&nbsp;Intercept {flow.request.host}
</a>
{ flow.request.path != "/" ? <a href="#" onClick={(e) =>{
onClick(e, flow.request.host + flow.request.path)
}}>
<i className="fa fa-fw fa-plus"></i>
&nbsp;Intercept {flow.request.host + flow.request.path}
</a> : null }
<a href="#" onClick={(e) =>{
onClick(e, `~m POST & ${flow.request.host}`)
}}>
<i className="fa fa-fw fa-plus"></i>
&nbsp;Intercept all POST requests from this host
</a>
</DropdownSubMenu>
)
}
SubMenu = connect(
state => ({
flow: state.flows.byId[state.flows.selected[0]],
intercept: state.settings.intercept,
}),
{
updateSettings,
}
)(SubMenu)
class HoverMenu extends Component {
constructor(props, context) {
super(props, context)
}
render() {
@ -28,24 +63,18 @@ class HoverMenu extends Component {
return null
}
return (
<Dropdown className="pull-left btn btn-default" btnClass="special" icon="fa-ellipsis-v" text="Actions">
<Dropdown className="pull-right" btnClass="special" icon="fa fa-fw fa-ellipsis-h" submenu={<SubMenu />}>
<a href="#" onClick={(e) =>{
this.onClick(e, flow.request.host)
e.preventDefault()
}}>
<i className="fa fa-fw fa-plus"></i>
&nbsp;Intercept {flow.request.host}
&nbsp;Copy as Curl
</a>
{ flow.request.path != "/" ? <a href="#" onClick={(e) =>{
this.onClick(e, flow.request.host + flow.request.path)
}}>
<i className="fa fa-fw fa-plus"></i>
&nbsp;Intercept {flow.request.host + flow.request.path}
</a> : null }
<a href="#" onClick={(e) =>{
this.onClick(e, `~m POST & ${flow.request.host}`)
e.preventDefault()
}}>
<i className="fa fa-fw fa-plus"></i>
&nbsp;Intercept all POST requests from this host
&nbsp;Download HAR
</a>
</Dropdown>
)
@ -55,9 +84,6 @@ class HoverMenu extends Component {
export default connect(
state => ({
flow: state.flows.byId[state.flows.selected[0]],
intercept: state.settings.intercept,
}),
{
updateSettings,
}
null
)(HoverMenu)

View file

@ -39,15 +39,17 @@ export default class Dropdown extends Component {
}
render() {
const {dropup, className, btnClass, text, children} = this.props
const {dropup, className, btnClass, text, icon, children, submenu} = this.props
return (
<div className={classnames((dropup ? 'dropup' : 'dropdown'), className, {open: this.state.open})}>
<a href='#' className={btnClass}
onClick={this.open}>
{icon ? <i className={icon}/> : null}
{text}
</a>
<ul className="dropdown-menu" role="menu">
{children.map((item, i) => <li key={i}> {item} </li>)}
{submenu}
</ul>
</div>
)

View file

@ -0,0 +1,73 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Manager, Reference, Popper } from 'react-popper';
import classnames from 'classnames'
export const Divider = () => <hr className="divider"/>
export default class DropdownSubMenu extends Component {
static propTypes = {
dropup: PropTypes.bool,
className: PropTypes.string,
}
static defaultProps = {
dropup: false
}
constructor(props, context) {
super(props, context)
this.state = {open: false}
this.close = this.close.bind(this)
this.open = this.open.bind(this)
}
close() {
this.setState({open: false})
document.removeEventListener('click', this.close)
}
open(e) {
e.preventDefault()
if (this.state.open) {
return
}
e.stopPropagation();
this.setState({open: !this.state.open})
document.addEventListener('click', this.close)
}
render() {
const {text, children} = this.props
return (
<li className="dropdown-submenu">
<Manager>
<Reference>
{({ ref }) => (
<a tabIndex="-1" ref={ref} href="#">{text}</a>
)}
</Reference>
<Popper
placement="bottom-end"
modifiers={[
{
name: 'offset',
options: {
offset: [0, 30],
}
},
]}
>
{({ ref, style, placement, arrowProps }) => (
<ul ref={ref} style={style} data-placement={placement} className="dropdown-menu pull-left">
{children.map((item, i) => <li key={i}> {item} </li>)}
</ul>
)}
</Popper>
</Manager>
</li>
)
}
}