2022-04-07 07:06:56 -05:00
|
|
|
import React, { useRef, useMemo } from 'react';
|
2022-02-10 23:06:24 -06:00
|
|
|
import DockLayout from 'rc-dock';
|
|
|
|
import { makeStyles } from '@material-ui/styles';
|
|
|
|
import PropTypes from 'prop-types';
|
2022-04-07 07:06:56 -05:00
|
|
|
import EventBus from './EventBus';
|
|
|
|
import getApiInstance from '../api_instance';
|
|
|
|
import url_for from 'sources/url_for';
|
|
|
|
import { PgIconButton } from '../components/Buttons';
|
|
|
|
import CloseIcon from '@material-ui/icons/CloseRounded';
|
|
|
|
import gettext from 'sources/gettext';
|
|
|
|
import {ExpandDialogIcon, MinimizeDialogIcon } from '../components/ExternalIcon';
|
2022-02-10 23:06:24 -06:00
|
|
|
|
|
|
|
|
|
|
|
const useStyles = makeStyles((theme)=>({
|
|
|
|
docklayout: {
|
|
|
|
height: '100%',
|
|
|
|
'& .dock-tab-active': {
|
|
|
|
color: theme.otherVars.activeColor,
|
|
|
|
'&::hover': {
|
|
|
|
color: theme.otherVars.activeColor,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'& .dock-ink-bar': {
|
|
|
|
height: '3px',
|
|
|
|
backgroundColor: theme.otherVars.activeBorder,
|
|
|
|
color: theme.otherVars.activeColor,
|
|
|
|
'&.dock-ink-bar-animated': {
|
|
|
|
transition: 'none !important',
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'& .dock-bar': {
|
|
|
|
paddingLeft: 0,
|
|
|
|
backgroundColor: theme.palette.background.default,
|
|
|
|
...theme.mixins.panelBorder.bottom,
|
2022-04-07 07:06:56 -05:00
|
|
|
'& .dock-nav-wrap': {
|
|
|
|
cursor: 'move',
|
|
|
|
}
|
2022-02-10 23:06:24 -06:00
|
|
|
},
|
|
|
|
'& .dock-panel': {
|
|
|
|
border: 'none',
|
2022-04-07 07:06:56 -05:00
|
|
|
'&.dragging': {
|
|
|
|
opacity: 0.6,
|
|
|
|
},
|
|
|
|
'& .dock': {
|
|
|
|
borderRadius: 'inherit',
|
|
|
|
},
|
2022-02-10 23:06:24 -06:00
|
|
|
'&.dock-style-dialogs': {
|
2022-04-07 07:06:56 -05:00
|
|
|
borderRadius: theme.shape.borderRadius,
|
2022-02-10 23:06:24 -06:00
|
|
|
'&.dock-panel.dragging': {
|
|
|
|
opacity: 1,
|
2022-04-25 07:41:39 -05:00
|
|
|
pointerEvents: 'visible',
|
2022-02-10 23:06:24 -06:00
|
|
|
},
|
|
|
|
'& .dock-ink-bar': {
|
|
|
|
height: '0px',
|
|
|
|
},
|
|
|
|
'& .dock-panel-drag-size-b-r': {
|
|
|
|
zIndex: 1020,
|
|
|
|
},
|
|
|
|
'& .dock-tab-active': {
|
|
|
|
color: theme.palette.text.primary,
|
|
|
|
fontWeight: 'bold',
|
|
|
|
'&::hover': {
|
|
|
|
color: theme.palette.text.primary,
|
|
|
|
}
|
|
|
|
},
|
2022-04-07 07:06:56 -05:00
|
|
|
},
|
|
|
|
'& .dock-tabpane': {
|
|
|
|
backgroundColor: theme.palette.background.default,
|
|
|
|
color: theme.palette.text.primary,
|
2022-09-07 08:50:03 -05:00
|
|
|
},
|
|
|
|
'& #id-schema-diff': {
|
|
|
|
overflowY: 'auto'
|
|
|
|
},
|
|
|
|
'& #id-results': {
|
|
|
|
overflowY: 'auto'
|
2022-02-10 23:06:24 -06:00
|
|
|
}
|
|
|
|
},
|
|
|
|
'& .dock-tab': {
|
|
|
|
minWidth: 'unset',
|
|
|
|
borderBottom: 'none',
|
|
|
|
marginRight: 0,
|
|
|
|
background: 'unset',
|
|
|
|
fontWeight: 'unset',
|
2022-04-07 07:06:56 -05:00
|
|
|
color: theme.palette.text.primary,
|
2022-02-10 23:06:24 -06:00
|
|
|
'&::hover': {
|
|
|
|
color: 'unset',
|
2022-04-07 07:06:56 -05:00
|
|
|
},
|
|
|
|
'& > div': {
|
|
|
|
padding: '4px 10px',
|
2022-04-20 08:29:28 -05:00
|
|
|
'&:focus': {
|
|
|
|
outline: '2px solid '+theme.otherVars.activeBorder,
|
|
|
|
}
|
2022-04-07 07:06:56 -05:00
|
|
|
},
|
|
|
|
'& .drag-initiator': {
|
|
|
|
display: 'flex',
|
|
|
|
'& .dock-tab-close-btn': {
|
|
|
|
color: theme.palette.text.primary,
|
|
|
|
position: 'unset',
|
|
|
|
marginLeft: '8px',
|
|
|
|
fontSize: '18px',
|
|
|
|
transition: 'none',
|
|
|
|
'&::before': {
|
|
|
|
content: '"\\00d7"',
|
|
|
|
position: 'relative',
|
|
|
|
top: '-5px',
|
|
|
|
}
|
|
|
|
}
|
2022-02-10 23:06:24 -06:00
|
|
|
}
|
|
|
|
},
|
2022-04-07 07:06:56 -05:00
|
|
|
'& .dock-extra-content': {
|
|
|
|
alignItems: 'center',
|
|
|
|
paddingRight: '10px',
|
|
|
|
},
|
2022-02-10 23:06:24 -06:00
|
|
|
'& .dock-vbox, & .dock-hbox .dock-vbox': {
|
|
|
|
'& .dock-divider': {
|
|
|
|
flexBasis: '1px',
|
|
|
|
transform: 'scaleY(8)',
|
|
|
|
'&::before': {
|
|
|
|
backgroundColor: theme.otherVars.borderColor,
|
|
|
|
display: 'block',
|
|
|
|
content: '""',
|
|
|
|
width: '100%',
|
|
|
|
transform: 'scaleY(0.125)',
|
|
|
|
height: '1px',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'& .dock-hbox, & .dock-vbox .dock-hbox': {
|
|
|
|
'& .dock-divider': {
|
|
|
|
flexBasis: '1px',
|
|
|
|
transform: 'scaleX(8)',
|
|
|
|
'&::before': {
|
|
|
|
backgroundColor: theme.otherVars.borderColor,
|
|
|
|
display: 'block',
|
|
|
|
content: '""',
|
|
|
|
height: '100%',
|
|
|
|
transform: 'scaleX(0.125)',
|
|
|
|
width: '1px',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'& .dock-content-animated': {
|
|
|
|
transition: 'none',
|
|
|
|
},
|
|
|
|
'& .dock-fbox': {
|
|
|
|
zIndex: 1060,
|
2022-04-07 07:06:56 -05:00
|
|
|
},
|
|
|
|
'& .dock-mbox': {
|
|
|
|
zIndex: 1080,
|
|
|
|
},
|
|
|
|
'& .drag-accept-reject::after': {
|
|
|
|
content: '',
|
2022-02-10 23:06:24 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
2022-04-07 07:06:56 -05:00
|
|
|
export const LayoutEventsContext = React.createContext();
|
2022-02-10 23:06:24 -06:00
|
|
|
|
|
|
|
export class LayoutHelper {
|
|
|
|
static getPanel(attrs) {
|
|
|
|
return {
|
|
|
|
cached: true,
|
2022-04-07 07:06:56 -05:00
|
|
|
group: 'default',
|
2022-04-22 07:47:01 -05:00
|
|
|
minWidth: 200,
|
2022-02-10 23:06:24 -06:00
|
|
|
...attrs,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
static close(docker, panelId) {
|
2022-04-07 07:06:56 -05:00
|
|
|
docker?.dockMove(docker.find(panelId), 'remove');
|
2022-02-10 23:06:24 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static focus(docker, panelId) {
|
2022-04-07 07:06:56 -05:00
|
|
|
docker?.updateTab(panelId, null, true);
|
2022-02-10 23:06:24 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static openDialog(docker, panelData, width=500, height=300) {
|
|
|
|
let panel = docker.find(panelData.id);
|
|
|
|
if(panel) {
|
|
|
|
docker.dockMove(panel, null, 'front');
|
|
|
|
} else {
|
|
|
|
let {width: lw, height: lh} = docker.getLayoutSize();
|
|
|
|
lw = (lw - width)/2;
|
|
|
|
lh = (lh - height)/2;
|
|
|
|
docker.dockMove({
|
|
|
|
x: lw,
|
|
|
|
y: lh,
|
|
|
|
w: width,
|
|
|
|
h: height,
|
|
|
|
tabs: [LayoutHelper.getPanel({
|
|
|
|
...panelData,
|
|
|
|
group: 'dialogs',
|
2022-04-07 07:06:56 -05:00
|
|
|
closable: false,
|
2022-02-10 23:06:24 -06:00
|
|
|
})],
|
|
|
|
}, null, 'float');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-07 07:06:56 -05:00
|
|
|
static isTabOpen(docker, panelId) {
|
|
|
|
return Boolean(docker.find(panelId));
|
|
|
|
}
|
|
|
|
|
2022-04-20 08:29:28 -05:00
|
|
|
static isTabVisible(docker, panelId) {
|
|
|
|
let panelData = docker.find(panelId);
|
|
|
|
return panelData?.parent?.activeId == panelData.id;
|
|
|
|
}
|
|
|
|
|
2022-02-10 23:06:24 -06:00
|
|
|
static openTab(docker, panelData, refTabId, direction, forceRerender=false) {
|
|
|
|
let panel = docker.find(panelData.id);
|
|
|
|
if(panel) {
|
|
|
|
if(forceRerender) {
|
|
|
|
docker.updateTab(panelData.id, LayoutHelper.getPanel(panelData), true);
|
|
|
|
} else {
|
|
|
|
LayoutHelper.focus(docker, panelData.id);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let tgtPanel = docker.find(refTabId);
|
|
|
|
docker.dockMove(LayoutHelper.getPanel(panelData), tgtPanel, direction);
|
|
|
|
}
|
|
|
|
}
|
2022-04-20 08:29:28 -05:00
|
|
|
|
|
|
|
static moveTo(direction) {
|
|
|
|
let dockBar = document.activeElement.closest('.dock')?.querySelector('.dock-bar.drag-initiator');
|
|
|
|
if(dockBar) {
|
|
|
|
let key = {
|
|
|
|
key: 'ArrowRight', keyCode: 39, which: 39, code: 'ArrowRight',
|
|
|
|
metaKey: false, ctrlKey: false, shiftKey: false, altKey: false,
|
|
|
|
bubbles: true,
|
|
|
|
};
|
|
|
|
if(direction == 'right') {
|
|
|
|
key = {
|
|
|
|
...key,
|
|
|
|
key: 'ArrowRight', keyCode: 39, which: 39, code: 'ArrowRight'
|
|
|
|
};
|
|
|
|
} else if(direction == 'left') {
|
|
|
|
key = {
|
|
|
|
...key,
|
|
|
|
key: 'ArrowLeft', keyCode: 37, which: 37, code: 'ArrowLeft',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
dockBar.dispatchEvent(new KeyboardEvent('keydown', key));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static switchPanel() {
|
|
|
|
let currDockPanel = document.activeElement.closest('.dock-panel.dock-style-default');
|
|
|
|
let dockLayoutPanels = currDockPanel?.closest('.dock-layout').querySelectorAll('.dock-panel.dock-style-default');
|
|
|
|
if(dockLayoutPanels?.length > 1) {
|
|
|
|
for(let i=0; i<dockLayoutPanels.length; i++) {
|
|
|
|
if(dockLayoutPanels[i] == currDockPanel) {
|
|
|
|
let newPanelIdx = (i+1)%dockLayoutPanels.length;
|
|
|
|
dockLayoutPanels[newPanelIdx]?.querySelector('.dock-tab.dock-tab-active .dock-tab-btn')?.focus();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-10 23:06:24 -06:00
|
|
|
}
|
|
|
|
|
2022-04-07 07:06:56 -05:00
|
|
|
function saveLayout(layoutObj, layoutId) {
|
|
|
|
let api = getApiInstance();
|
|
|
|
if(!layoutId || !layoutObj) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const formData = new FormData();
|
|
|
|
formData.append('setting', layoutId);
|
|
|
|
formData.append('value', JSON.stringify(layoutObj.saveLayout()));
|
|
|
|
api.post(url_for('settings.store_bulk'), formData)
|
|
|
|
.catch(()=>{/* No need to throw error */});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDialogsGroup() {
|
|
|
|
return {
|
|
|
|
disableDock: true,
|
|
|
|
tabLocked: true,
|
|
|
|
floatable: 'singleTab',
|
|
|
|
panelExtra: (panelData, context) => (
|
|
|
|
<div>
|
|
|
|
<PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={()=>{
|
|
|
|
context.dockMove(panelData, null, 'remove');
|
|
|
|
}} />
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDefaultGroup() {
|
|
|
|
return {
|
|
|
|
maximizable: false,
|
|
|
|
panelExtra: (panelData, context) => {
|
|
|
|
let icon = <ExpandDialogIcon style={{width: '0.7em'}}/>;
|
|
|
|
let title = gettext('Maximise');
|
|
|
|
if(panelData?.parent?.mode == 'maximize') {
|
|
|
|
icon = <MinimizeDialogIcon />;
|
|
|
|
title = gettext('Restore');
|
|
|
|
}
|
|
|
|
return <div>
|
|
|
|
<PgIconButton title={title} icon={icon} size="xs" noBorder onClick={()=>{
|
|
|
|
context.dockMove(panelData, null, 'maximize');
|
|
|
|
}} />
|
|
|
|
</div>;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function Layout({groups, getLayoutInstance, layoutId, savedLayout, ...props}) {
|
2022-02-10 23:06:24 -06:00
|
|
|
const classes = useStyles();
|
2022-04-07 07:06:56 -05:00
|
|
|
const layoutObj = useRef();
|
2022-02-10 23:06:24 -06:00
|
|
|
const defaultGroups = React.useMemo(()=>({
|
2022-04-07 07:06:56 -05:00
|
|
|
'dialogs': getDialogsGroup(),
|
|
|
|
'default': getDefaultGroup(),
|
2022-02-10 23:06:24 -06:00
|
|
|
...groups,
|
|
|
|
}), [groups]);
|
2022-04-07 07:06:56 -05:00
|
|
|
|
|
|
|
const layoutEventBus = React.useRef(new EventBus());
|
|
|
|
return useMemo(()=>(
|
2022-02-10 23:06:24 -06:00
|
|
|
<div className={classes.docklayout}>
|
2022-04-07 07:06:56 -05:00
|
|
|
<LayoutEventsContext.Provider value={layoutEventBus.current}>
|
|
|
|
<DockLayout
|
|
|
|
style={{
|
|
|
|
height: '100%',
|
|
|
|
}}
|
|
|
|
ref={(obj)=>{
|
|
|
|
layoutObj.current = obj;
|
|
|
|
if(layoutObj.current) {
|
|
|
|
layoutObj.current.resetLayout = ()=>{
|
|
|
|
layoutObj.current.loadLayout(props.defaultLayout);
|
|
|
|
saveLayout(layoutObj.current, layoutId);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
getLayoutInstance?.(layoutObj.current);
|
|
|
|
try {
|
|
|
|
layoutObj.current?.loadLayout(JSON.parse(savedLayout));
|
|
|
|
} catch {
|
|
|
|
/* Fallback to default */
|
|
|
|
layoutObj.current?.loadLayout(props.defaultLayout);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
groups={defaultGroups}
|
|
|
|
onLayoutChange={(_l, currentTabId, direction)=>{
|
|
|
|
saveLayout(layoutObj.current, layoutId);
|
2022-06-13 03:56:01 -05:00
|
|
|
direction = direction == 'update' ? 'active' : direction;
|
2022-04-07 07:06:56 -05:00
|
|
|
if(Object.values(LAYOUT_EVENTS).indexOf(direction) > -1) {
|
|
|
|
layoutEventBus.current.fireEvent(LAYOUT_EVENTS[direction.toUpperCase()], currentTabId);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
{...props}
|
|
|
|
/>
|
|
|
|
</LayoutEventsContext.Provider>
|
2022-02-10 23:06:24 -06:00
|
|
|
</div>
|
2022-04-07 07:06:56 -05:00
|
|
|
), []);
|
2022-02-10 23:06:24 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
Layout.propTypes = {
|
|
|
|
groups: PropTypes.object,
|
2022-04-07 07:06:56 -05:00
|
|
|
getLayoutInstance: PropTypes.func,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const LAYOUT_EVENTS = {
|
|
|
|
ACTIVE: 'active',
|
|
|
|
REMOVE: 'remove',
|
|
|
|
FLOAT: 'float',
|
|
|
|
FRONT: 'front',
|
|
|
|
MAXIMIZE: 'maximize',
|
|
|
|
MOVE: 'move',
|
2022-02-10 23:06:24 -06:00
|
|
|
};
|