import React, { useRef, useMemo } from 'react'; import DockLayout from 'rc-dock'; import { makeStyles } from '@material-ui/styles'; import PropTypes from 'prop-types'; 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'; 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, '& .dock-nav-wrap': { cursor: 'move', } }, '& .dock-panel': { border: 'none', '&.dragging': { opacity: 0.6, pointerEvents: 'visible', }, '& .dock': { borderRadius: 'inherit', }, '&.dock-style-dialogs': { borderRadius: theme.shape.borderRadius, '&.dock-panel.dragging': { opacity: 1, }, '& .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, } }, }, '& .dock-tabpane': { backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, } }, '& .dock-tab': { minWidth: 'unset', borderBottom: 'none', marginRight: 0, background: 'unset', fontWeight: 'unset', color: theme.palette.text.primary, '&::hover': { color: 'unset', }, '& > div': { padding: '4px 10px', }, '& .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', } } } }, '& .dock-extra-content': { alignItems: 'center', paddingRight: '10px', }, '& .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, }, '& .dock-mbox': { zIndex: 1080, }, '& .drag-accept-reject::after': { content: '', } } })); export const LayoutEventsContext = React.createContext(); export class LayoutHelper { static getPanel(attrs) { return { cached: true, group: 'default', ...attrs, }; } static close(docker, panelId) { docker?.dockMove(docker.find(panelId), 'remove'); } static focus(docker, panelId) { docker?.updateTab(panelId, null, true); } 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', closable: false, })], }, null, 'float'); } } static isTabOpen(docker, panelId) { return Boolean(docker.find(panelId)); } 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); } } } 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) => (
} size="xs" noBorder onClick={()=>{ context.dockMove(panelData, null, 'remove'); }} />
) }; } function getDefaultGroup() { return { maximizable: false, panelExtra: (panelData, context) => { let icon = ; let title = gettext('Maximise'); if(panelData?.parent?.mode == 'maximize') { icon = ; title = gettext('Restore'); } return
{ context.dockMove(panelData, null, 'maximize'); }} />
; } }; } export default function Layout({groups, getLayoutInstance, layoutId, savedLayout, ...props}) { const classes = useStyles(); const layoutObj = useRef(); const defaultGroups = React.useMemo(()=>({ 'dialogs': getDialogsGroup(), 'default': getDefaultGroup(), ...groups, }), [groups]); const layoutEventBus = React.useRef(new EventBus()); return useMemo(()=>(
{ 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); if(Object.values(LAYOUT_EVENTS).indexOf(direction) > -1) { layoutEventBus.current.fireEvent(LAYOUT_EVENTS[direction.toUpperCase()], currentTabId); } }} {...props} />
), []); } Layout.propTypes = { groups: PropTypes.object, getLayoutInstance: PropTypes.func, }; export const LAYOUT_EVENTS = { ACTIVE: 'active', REMOVE: 'remove', FLOAT: 'float', FRONT: 'front', MAXIMIZE: 'maximize', MOVE: 'move', };