fix(ui): design review fixes
This commit is contained in:
parent
6e24da549a
commit
366314193e
13 changed files with 140 additions and 73 deletions
|
|
@ -1,6 +1,6 @@
|
|||
.tabs {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
&.bordered {
|
||||
border-bottom: solid thin $gray-light;
|
||||
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
.tab {
|
||||
padding: 14px 15px;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
color: $gray-darkest;
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ function UserCard({
|
|||
<span className="mx-1 font-bold text-xl">·</span>
|
||||
<Tooltip
|
||||
theme='light'
|
||||
delay={0}
|
||||
hideOnClick="persistent"
|
||||
arrow
|
||||
interactive
|
||||
html={(
|
||||
|
|
@ -89,7 +91,11 @@ function UserCard({
|
|||
disabled={false}
|
||||
on="hover"
|
||||
>
|
||||
<span className="color-teal cursor-pointer">More</span>
|
||||
<span
|
||||
className="color-teal cursor-pointer"
|
||||
>
|
||||
More
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -74,11 +74,11 @@ function PageInsightsPanel({
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => { setActiveTab(''); }}
|
||||
className="ml-auto flex items-center justify-center bg-white cursor-pointer"
|
||||
>
|
||||
<Icon name="close" size="18" />
|
||||
</div>
|
||||
onClick={() => { setActiveTab(''); }}
|
||||
className="ml-auto flex items-center justify-center bg-white cursor-pointer"
|
||||
>
|
||||
<Icon name="close" size="18" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4 flex items-center">
|
||||
<div className="mr-2 flex-shrink-0">In Page</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
padding: 10px;
|
||||
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 3px;
|
||||
background-color: white;
|
||||
background-color: $gray-lightest;
|
||||
margin-bottom: 15px;
|
||||
|
||||
& .top {
|
||||
|
|
@ -40,5 +40,5 @@
|
|||
}
|
||||
|
||||
.active {
|
||||
background-color: #f9ffff;
|
||||
}
|
||||
background-color: $light-blue-bg;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const ControlButton = ({
|
|||
noIcon,
|
||||
}) => (
|
||||
<button
|
||||
className={ cn(stl.controlButton, { [stl.disabled]: disabled, [stl.active]: active }, "relative", containerClassName) }
|
||||
className={ cn(stl.controlButton, { [stl.disabled]: disabled }, "relative", active ? 'border-b-2 border-main' : 'rounded',containerClassName) }
|
||||
onClick={ onClick }
|
||||
id={"control-button-" + label.toLowerCase()}
|
||||
disabled={disabled}
|
||||
|
|
@ -26,7 +26,7 @@ const ControlButton = ({
|
|||
{ count > 0 && <div className={ stl.countLabel }>{ count }</div>}
|
||||
{ hasErrors && <div className={ stl.errorSymbol } /> }
|
||||
{!noIcon && <Icon name={ icon } size={size} color="gray-dark"/>}
|
||||
{!noLabel && <span className={ cn(stl.label, labelClassName) }>{ label }</span>}
|
||||
{!noLabel && <span className={ cn(stl.label, labelClassName, active ? 'color-main' : 'color-gray-darkest') }>{ label }</span>}
|
||||
</button>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
import {
|
||||
connectPlayer,
|
||||
STORAGE_TYPES,
|
||||
selectStorageType,
|
||||
selectStorageListNow,
|
||||
} from 'Player/store';
|
||||
import LiveTag from 'Shared/LiveTag';
|
||||
|
||||
import { Icon } from 'UI';
|
||||
import { toggleInspectorMode } from 'Player';
|
||||
import {
|
||||
fullscreenOn,
|
||||
|
|
@ -22,7 +24,6 @@ import {
|
|||
GRAPHQL,
|
||||
FETCH,
|
||||
EXCEPTIONS,
|
||||
LONGTASKS,
|
||||
INSPECTOR,
|
||||
} from 'Duck/components/player';
|
||||
import { ReduxTime } from './Time';
|
||||
|
|
@ -30,6 +31,7 @@ import Timeline from './Timeline';
|
|||
import ControlButton from './ControlButton';
|
||||
|
||||
import styles from './controls.module.css';
|
||||
import { Tooltip } from 'react-tippy';
|
||||
|
||||
|
||||
function getStorageIconName(type) {
|
||||
|
|
@ -96,7 +98,7 @@ function getStorageName(type) {
|
|||
showExceptions: state.exceptionsList.length > 0,
|
||||
showLongtasks: state.longtasksList.length > 0,
|
||||
}))
|
||||
@connect((state, props) => {
|
||||
@connect((state, props) => {
|
||||
const permissions = state.getIn([ 'user', 'account', 'permissions' ]) || [];
|
||||
const isEnterprise = state.getIn([ 'user', 'account', 'edition' ]) === 'ee';
|
||||
return {
|
||||
|
|
@ -131,9 +133,9 @@ export default class Controls extends React.Component {
|
|||
nextProps.live !== this.props.live ||
|
||||
nextProps.livePlay !== this.props.livePlay ||
|
||||
nextProps.playing !== this.props.playing ||
|
||||
nextProps.completed !== this.props.completed ||
|
||||
nextProps.skip !== this.props.skip ||
|
||||
nextProps.skipToIssue !== this.props.skipToIssue ||
|
||||
nextProps.completed !== this.props.completed ||
|
||||
nextProps.skip !== this.props.skip ||
|
||||
nextProps.skipToIssue !== this.props.skipToIssue ||
|
||||
nextProps.speed !== this.props.speed ||
|
||||
nextProps.disabled !== this.props.disabled ||
|
||||
nextProps.fullscreenDisabled !== this.props.fullscreenDisabled ||
|
||||
|
|
@ -149,7 +151,7 @@ export default class Controls extends React.Component {
|
|||
nextProps.storageCount !== this.props.storageCount ||
|
||||
nextProps.storageType !== this.props.storageType ||
|
||||
nextProps.showStorage !== this.props.showStorage ||
|
||||
nextProps.showProfiler !== this.props.showProfiler ||
|
||||
nextProps.showProfiler !== this.props.showProfiler ||
|
||||
nextProps.showGraphql !== this.props.showGraphql ||
|
||||
nextProps.showFetch !== this.props.showFetch ||
|
||||
nextProps.fetchCount !== this.props.fetchCount ||
|
||||
|
|
@ -162,14 +164,19 @@ export default class Controls extends React.Component {
|
|||
}
|
||||
|
||||
onKeyDown = (e) => {
|
||||
console.log(e.key, e.target)
|
||||
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
|
||||
return;
|
||||
}
|
||||
if (this.props.inspectorMode) return;
|
||||
if (this.props.inspectorMode) {
|
||||
if (e.key === 'Esc' || e.key === 'Escape') {
|
||||
toggleInspectorMode();
|
||||
}
|
||||
};
|
||||
// if (e.key === ' ') {
|
||||
// document.activeElement.blur();
|
||||
// this.props.togglePlay();
|
||||
// }
|
||||
// }
|
||||
if (e.key === 'Esc' || e.key === 'Escape') {
|
||||
this.props.fullscreenOff();
|
||||
}
|
||||
|
|
@ -204,34 +211,46 @@ export default class Controls extends React.Component {
|
|||
let label;
|
||||
let icon;
|
||||
if (completed) {
|
||||
icon = 'redo';
|
||||
icon = 'arrow-clockwise';
|
||||
label = 'Replay this session'
|
||||
} else if (playing) {
|
||||
icon = 'play-fill-new';
|
||||
} else {
|
||||
icon = 'pause-fill';
|
||||
label = 'Pause';
|
||||
} else {
|
||||
icon = 'play-fill-new';
|
||||
label = 'Pause';
|
||||
label = 'Play'
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={this.props.togglePlay}
|
||||
className="mr-2 hover-main color-gray-medium cursor-pointer rounded hover:bg-gray-light-shade"
|
||||
<Tooltip
|
||||
delay={0}
|
||||
position="top"
|
||||
title={label}
|
||||
interactive
|
||||
hideOnClick="persistent"
|
||||
>
|
||||
<Icon name={icon} size="36" color="inherit" />
|
||||
</div>
|
||||
<div
|
||||
onClick={this.props.togglePlay}
|
||||
className="mr-2 hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade"
|
||||
>
|
||||
<Icon name={icon} size="36" color="inherit" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
controlIcon = (icon, size, action, isBackwards, additionalClasses) =>
|
||||
<div
|
||||
onClick={ action }
|
||||
className={cn("py-1 px-2 color-gray-medium hover-main cursor-pointer", additionalClasses)}
|
||||
controlIcon = (icon, size, action, isBackwards, additionalClasses) =>
|
||||
<div
|
||||
onClick={ action }
|
||||
className={cn("py-1 px-2 hover-main cursor-pointer", additionalClasses)}
|
||||
style={{ transform: isBackwards ? 'rotate(180deg)' : '' }}
|
||||
>
|
||||
<Icon name={icon} size={size} color="inherit" />
|
||||
</div>
|
||||
|
||||
render() {
|
||||
const {
|
||||
const {
|
||||
bottomBlock,
|
||||
toggleBottomBlock,
|
||||
live,
|
||||
|
|
@ -239,7 +258,6 @@ export default class Controls extends React.Component {
|
|||
skip,
|
||||
speed,
|
||||
disabled,
|
||||
fullscreenDisabled,
|
||||
logCount,
|
||||
logRedCount,
|
||||
resourceRedCount,
|
||||
|
|
@ -256,11 +274,9 @@ export default class Controls extends React.Component {
|
|||
showFetch,
|
||||
fetchCount,
|
||||
graphqlCount,
|
||||
showLongtasks,
|
||||
exceptionsCount,
|
||||
showExceptions,
|
||||
fullscreen,
|
||||
skipToIssue,
|
||||
fullscreen,
|
||||
inspectorMode,
|
||||
closedLive,
|
||||
} = this.props;
|
||||
|
|
@ -285,23 +301,41 @@ export default class Controls extends React.Component {
|
|||
)}
|
||||
|
||||
<div className="rounded ml-4 bg-active-blue border border-active-blue-border flex items-stretch">
|
||||
{this.controlIcon("skip-forward-fill", 18, this.backTenSeconds, true, 'hover:bg-active-blue-border')}
|
||||
<Tooltip
|
||||
title='Rewind 10s'
|
||||
delay={0}
|
||||
position="top"
|
||||
>
|
||||
{this.controlIcon("skip-forward-fill", 18, this.backTenSeconds, true, 'hover:bg-active-blue-border color-main h-full')}
|
||||
</Tooltip>
|
||||
<div className='p-1 border-l border-r bg-active-blue-border border-active-blue-border'>10s</div>
|
||||
{this.controlIcon("skip-forward-fill", 18, this.forthTenSeconds, false, 'hover:bg-active-blue-border')}
|
||||
<Tooltip
|
||||
title='Forward 10s'
|
||||
delay={0}
|
||||
position="top"
|
||||
>
|
||||
{this.controlIcon("skip-forward-fill", 18, this.forthTenSeconds, false, 'hover:bg-active-blue-border color-main h-full')}
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{!live &&
|
||||
<div className='flex items-center mx-4'>
|
||||
<button
|
||||
className={ styles.speedButton }
|
||||
onClick={ this.props.toggleSpeed }
|
||||
data-disabled={ disabled }
|
||||
<Tooltip
|
||||
title='Playback speed'
|
||||
delay={0}
|
||||
position="top"
|
||||
>
|
||||
<div>{ speed + 'x' }</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={ styles.speedButton }
|
||||
onClick={ this.props.toggleSpeed }
|
||||
data-disabled={ disabled }
|
||||
>
|
||||
<div>{ speed + 'x' }</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<button
|
||||
className={ cn(styles.skipIntervalButton, { [styles.withCheckIcon]: skip }) }
|
||||
className={ cn(styles.skipIntervalButton, { [styles.withCheckIcon]: skip, [styles.active]: skip }) }
|
||||
onClick={ this.props.toggleSkip }
|
||||
data-disabled={ disabled }
|
||||
>
|
||||
|
|
@ -327,86 +361,94 @@ export default class Controls extends React.Component {
|
|||
{!live && (
|
||||
<ControlButton
|
||||
disabled={ disabled && !inspectorMode }
|
||||
active={ bottomBlock === INSPECTOR }
|
||||
active={ inspectorMode }
|
||||
onClick={ toggleInspectorMode }
|
||||
noIcon
|
||||
labelClassName="text-base font-semibold"
|
||||
label="INSPECT"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
)}
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(CONSOLE) }
|
||||
active={ bottomBlock === CONSOLE }
|
||||
active={ bottomBlock === CONSOLE && !inspectorMode}
|
||||
label="CONSOLE"
|
||||
noIcon
|
||||
labelClassName="text-base font-semibold"
|
||||
count={ logCount }
|
||||
hasErrors={ logRedCount > 0 }
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
{ !live &&
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(NETWORK) }
|
||||
active={ bottomBlock === NETWORK }
|
||||
active={ bottomBlock === NETWORK && !inspectorMode }
|
||||
label="NETWORK"
|
||||
hasErrors={ resourceRedCount > 0 }
|
||||
noIcon
|
||||
labelClassName="text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
}
|
||||
{!live &&
|
||||
{!live &&
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(PERFORMANCE) }
|
||||
active={ bottomBlock === PERFORMANCE }
|
||||
active={ bottomBlock === PERFORMANCE && !inspectorMode }
|
||||
label="PERFORMANCE"
|
||||
noIcon
|
||||
labelClassName="text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
}
|
||||
{showFetch &&
|
||||
<ControlButton
|
||||
disabled={disabled}
|
||||
onClick={ ()=> toggleBottomBlock(FETCH) }
|
||||
active={ bottomBlock === FETCH }
|
||||
active={ bottomBlock === FETCH && !inspectorMode }
|
||||
hasErrors={ fetchRedCount > 0 }
|
||||
count={ fetchCount }
|
||||
label="FETCH"
|
||||
noIcon
|
||||
labelClassName="text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
}
|
||||
{ !live && showGraphql &&
|
||||
<ControlButton
|
||||
disabled={disabled}
|
||||
onClick={ ()=> toggleBottomBlock(GRAPHQL) }
|
||||
active={ bottomBlock === GRAPHQL }
|
||||
active={ bottomBlock === GRAPHQL && !inspectorMode }
|
||||
count={ graphqlCount }
|
||||
label="GRAPHQL"
|
||||
noIcon
|
||||
labelClassName="text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
}
|
||||
{ !live && showStorage &&
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(STORAGE) }
|
||||
active={ bottomBlock === STORAGE }
|
||||
active={ bottomBlock === STORAGE && !inspectorMode }
|
||||
count={ storageCount }
|
||||
label={ getStorageName(storageType) }
|
||||
noIcon
|
||||
labelClassName="text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
}
|
||||
{ showExceptions &&
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(EXCEPTIONS) }
|
||||
active={ bottomBlock === EXCEPTIONS }
|
||||
active={ bottomBlock === EXCEPTIONS && !inspectorMode }
|
||||
label="EXCEPTIONS"
|
||||
noIcon
|
||||
labelClassName="text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
count={ exceptionsCount }
|
||||
hasErrors={ exceptionsCount > 0 }
|
||||
/>
|
||||
|
|
@ -415,10 +457,11 @@ export default class Controls extends React.Component {
|
|||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(STACKEVENTS) }
|
||||
active={ bottomBlock === STACKEVENTS }
|
||||
active={ bottomBlock === STACKEVENTS && !inspectorMode }
|
||||
label="EVENTS"
|
||||
noIcon
|
||||
labelClassName="text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
count={ stackCount }
|
||||
hasErrors={ stackRedCount > 0 }
|
||||
/>
|
||||
|
|
@ -427,16 +470,24 @@ export default class Controls extends React.Component {
|
|||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(PROFILER) }
|
||||
active={ bottomBlock === PROFILER }
|
||||
active={ bottomBlock === PROFILER && !inspectorMode }
|
||||
count={ profilesCount }
|
||||
label="PROFILER"
|
||||
noIcon
|
||||
labelClassName="text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
}
|
||||
}
|
||||
{ !live && <div className={cn(styles.divider, 'h-full')} /> }
|
||||
{ !live &&
|
||||
this.controlIcon("arrows-angle-extend", 18, this.props.fullscreenOn, false, "rounded hover:bg-gray-light-shade")
|
||||
{ !live && (
|
||||
<Tooltip
|
||||
title="Fullscreen"
|
||||
delay={0}
|
||||
position="top"
|
||||
>
|
||||
{this.controlIcon("arrows-angle-extend", 18, this.props.fullscreenOn, false, "rounded hover:bg-gray-light-shade color-gray-medium")}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px 10px;
|
||||
padding: 10px 5px;
|
||||
cursor: pointer;
|
||||
border-top: 0px;
|
||||
border-left: 0px;
|
||||
border-right: 0px;
|
||||
min-width: 60px;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
&.active, &:hover {
|
||||
&:hover {
|
||||
background-color: $gray-lightest;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
|
@ -19,7 +21,7 @@
|
|||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: red;
|
||||
bottom: 0px;
|
||||
bottom: 2px;
|
||||
right: 0px;
|
||||
position: absolute;
|
||||
}
|
||||
|
|
@ -44,7 +46,6 @@
|
|||
& .label {
|
||||
/* padding-top: 5px; */
|
||||
font-size: 10px;
|
||||
color: $gray-darkest;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,9 @@
|
|||
background-color: $gray-lightest;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
&.active {
|
||||
background: repeating-linear-gradient( 125deg, #efefef, #efefef 3px, #ddd 3px, #efefef 5px );
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
top: 3px;
|
||||
height: 10px;
|
||||
bottom: 0;
|
||||
background-color: $gray-light;
|
||||
background: repeating-linear-gradient( 125deg, #efefef, #efefef 3px, #ddd 3px, #efefef 5px );
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@
|
|||
width: 2px;
|
||||
height: 10px;
|
||||
background: $main;
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
/* top: 0; */
|
||||
/* bottom: 0; */
|
||||
|
|
|
|||
|
|
@ -104,9 +104,8 @@ export default class PlayerBlockHeader extends React.PureComponent {
|
|||
const TABS = [ this.props.tabs.EVENTS, this.props.tabs.HEATMAPS ].map(tab => ({ text: tab, key: tab }));
|
||||
return (
|
||||
<div className={ cn(stl.header, "flex justify-between", { "hidden" : fullscreen}) }>
|
||||
<div className="flex w-full items-center">
|
||||
|
||||
<BackLink label="Back" onClick={this.backHandler} className="h-full" />
|
||||
<div className="flex w-full items-center" onClick={this.backHandler} >
|
||||
<BackLink label="Back" className="h-full" />
|
||||
<div className={ stl.divider } />
|
||||
<UserCard
|
||||
className=""
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function SubHeader(props) {
|
|||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 hover:bg-gray-light-shade rounded-md p-1">
|
||||
<div className="mx-4 hover:bg-gray-light-shade rounded-md p-1">
|
||||
<Bookmark noMargin />
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
|||
4
frontend/app/svg/icons/arrow-clockwise.svg
Normal file
4
frontend/app/svg/icons/arrow-clockwise.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
|
||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 310 B |
|
|
@ -31,7 +31,7 @@ module.exports = {
|
|||
"bg-blue": "#e3e6ff",
|
||||
"active-blue-border": "#D0D4F2",
|
||||
pink: "#ffb9b9",
|
||||
|
||||
"light-blue-bg": "#E5F7F7",
|
||||
|
||||
white: "#fff",
|
||||
borderColor: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue