nope/resources/ui/layout/layout.tsx

534 lines
18 KiB
TypeScript
Raw Normal View History

2020-09-02 05:49:53 +00:00
/**
* @author Martin Karkowski
* @email m.karkowski@zema.de
* @create date 2019-02-20 09:19:06
2020-10-29 18:20:42 +00:00
* @modify date 2020-10-29 09:55:30
2020-09-02 05:49:53 +00:00
* @desc [description]
2020-10-29 18:20:42 +00:00
*
* A Basic Layout.
* I uses a Toolbar, Sidebar
*
* Toast are implemented by https://fkhadra.github.io/react-toastify/introduction
2020-09-02 05:49:53 +00:00
*/
import React from 'react';
2020-10-26 07:39:34 +00:00
import { Col, Container, Modal, Nav, Row } from 'react-bootstrap';
2020-09-07 05:21:53 +00:00
import { FaTimesCircle } from 'react-icons/fa';
2020-10-29 18:20:42 +00:00
import { toast, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { rgetattr, rsetattr } from '../../../lib/helpers/objectMethods';
import DynamicRenderer from '../dynamic/dynamicRenderer';
import { IHotKeyAction } from './interfaces/IHotkeyAction';
import { IModalSettings } from './interfaces/IModalSettings';
import { ITab } from './interfaces/ITab';
import Selection, { SelectionProps } from './selection';
import Toolbar, { ToolbarProps } from './toolbar';
2020-09-02 05:49:53 +00:00
const mainStyle = {
2020-10-29 21:20:47 +00:00
height: '89vh',
2020-09-02 05:49:53 +00:00
}
const sidebarStyle = {
padding: '10px',
2020-10-29 21:20:47 +00:00
// background: "primary",
overflow: 'scroll'
2020-09-02 05:49:53 +00:00
}
const contentStyle = {
2020-10-29 21:20:47 +00:00
// padding: '10px',
2020-09-02 15:00:03 +00:00
}
2020-10-29 18:20:42 +00:00
export interface ILayoutProps<Template, CallbackData> extends SelectionProps<Template>, ToolbarProps<CallbackData> {
2020-09-02 15:00:03 +00:00
onResize?: () => void;
2020-10-29 18:20:42 +00:00
onMount?: (ref: React.RefObject<any>, mainRef: React.RefObject<any>, layout: Layout<Template,CallbackData>) => void;
2020-09-02 15:00:03 +00:00
onUnmount?: () => void;
2020-09-07 11:05:40 +00:00
onNewTab?: () => Promise<ITab | false>;
2020-10-29 18:20:42 +00:00
onTabSelect?: (oldTabId: ITab, newTabId: ITab) => Promise<boolean>;
onTabDelete?: (tabId: ITab, forced?: boolean) => Promise<boolean>;
2020-10-25 20:14:51 +00:00
onNoTabSelected?: () => Promise<void>;
2020-09-07 05:21:53 +00:00
tabs?: {
2020-09-07 11:05:40 +00:00
allowNewTabs?: boolean;
2020-09-07 05:21:53 +00:00
active: string,
items: ITab[]
}
2020-10-29 18:20:42 +00:00
hotkeys?: IHotKeyAction<CallbackData>[]
2020-09-02 15:00:03 +00:00
}
2020-10-26 07:39:34 +00:00
2020-10-29 18:20:42 +00:00
export interface ILayoutState<Template, CallbackData> extends ILayoutProps<Template, CallbackData> {
modalSettings?: IModalSettings
modalVisible?: boolean
2020-09-02 15:00:03 +00:00
}
2020-10-29 18:20:42 +00:00
class Layout<Template,CallbackData> extends React.Component<ILayoutProps<Template, CallbackData>, ILayoutState<Template, CallbackData>> {
2020-09-02 15:00:03 +00:00
2020-10-29 18:20:42 +00:00
protected _mainRef: React.RefObject<any>;
2020-09-02 15:00:03 +00:00
protected _ref: React.RefObject<any>;
protected _handleResize: () => void;
2020-10-29 18:20:42 +00:00
protected _handleKeyDown: (event) => void;
protected _handleKeyUp: (event) => void;
protected _handleMouse: (event: MouseEvent) => void;
public hotkeysEnabled = true;
public pressedKey: string = null;
public currentMousePosition: MouseEvent;
2020-09-02 15:00:03 +00:00
2020-10-29 18:20:42 +00:00
/**
* Function will be called if the Item has been rendered sucessfully.
*/
2020-09-02 15:00:03 +00:00
componentDidMount() {
2020-10-29 18:20:42 +00:00
2020-09-02 15:00:03 +00:00
const _this = this;
2020-10-29 18:20:42 +00:00
// Listening to resizing Events
2020-09-02 15:00:03 +00:00
function handleResize() {
2020-10-29 18:20:42 +00:00
if (typeof _this.state.onResize === 'function') {
_this.state.onResize();
}
}
function handleKeyDown(event){
console.log(event.code)
if (!_this.hotkeysEnabled || _this.state.hotkeys?.length === 0) {
return;
}
if (_this.pressedKey !== event.code) {
_this.pressedKey = event.code;
try {
for (const hotkey of _this.state.hotkeys || []) {
if (hotkey.key === event.code) {
hotkey.onPress(_this.state.generateData(),event)
break;
}
}
} catch (e) {
}
}
}
function handleKeyUp(event){
if (!_this.hotkeysEnabled || _this.state.hotkeys?.length === 0) {
return;
2020-09-02 15:00:03 +00:00
}
2020-10-29 18:20:42 +00:00
_this.pressedKey = '';
try {
for (const hotkey of _this.state.hotkeys || []) {
if (hotkey.key === event.code && hotkey.onRelease) {
hotkey.onRelease(_this.state.generateData(),event)
break;
}
}
} catch (e) {
}
}
function handleMouse(event){
_this.currentMousePosition = event;
2020-09-02 15:00:03 +00:00
}
2020-10-29 18:20:42 +00:00
if (typeof _this.state.onMount === 'function') {
_this.state.onMount(_this._ref, _this._mainRef, _this);
2020-09-02 15:00:03 +00:00
}
this._handleResize = handleResize;
2020-10-29 18:20:42 +00:00
this._handleKeyDown = handleKeyDown;
this._handleKeyUp = handleKeyUp;
this._handleMouse = handleMouse;
2020-09-02 15:00:03 +00:00
window.addEventListener('resize', this._handleResize);
2020-10-29 18:20:42 +00:00
window.addEventListener('keydown', this._handleKeyDown);
window.addEventListener('keyup',this._handleKeyUp);
window.addEventListener('mousemove',this._handleMouse);
2020-09-02 15:00:03 +00:00
}
2020-10-29 18:20:42 +00:00
/**
* Function, that will be called before the network fails.
*/
2020-09-02 15:00:03 +00:00
componentWillUnmount() {
window.removeEventListener('resize', this._handleResize);
2020-10-29 18:20:42 +00:00
window.removeEventListener('keydown', this._handleKeyDown);
window.removeEventListener('keyup',this._handleKeyUp);
window.removeEventListener('mousemove',this._handleMouse);
2020-09-02 15:00:03 +00:00
// Call the unmount
2020-10-29 18:20:42 +00:00
if (typeof this.state.onUnmount === 'function') {
this.state.onUnmount();
2020-09-02 15:00:03 +00:00
}
}
constructor(props) {
super(props);
2020-10-29 18:20:42 +00:00
this._mainRef = React.createRef();
2020-09-02 15:00:03 +00:00
this._ref = React.createRef();
2020-10-29 18:20:42 +00:00
this.state = Object.assign({
modalSettings: null,
modalVisible : false
},this.props);
2020-09-07 05:21:53 +00:00
}
2020-10-29 18:20:42 +00:00
public openPopup(settings: IModalSettings) {
const modalSettings = settings;
const _this = this;
2020-10-26 07:39:34 +00:00
2020-10-29 18:20:42 +00:00
const _close = () => {
_this.setState({
modalVisible: false
});
};
2020-10-26 07:39:34 +00:00
2020-10-29 18:20:42 +00:00
const _originalFunction = modalSettings.onHide;
modalSettings.onHide = (...args) => {
if (typeof _originalFunction === 'function') {
_originalFunction(_close);
} else {
_close();
}
}
_this.setState({
modalVisible: true,
modalSettings: modalSettings
});
// Return the Close Method.
return _close;
}
public getDialogData<T = any>(settings: IModalSettings) {
2020-10-26 07:39:34 +00:00
const _this = this;
2020-10-29 18:20:42 +00:00
let close: () => void;
return new Promise<T>((resolve, reject) => {
// Adapt the Settings:
const _onSubmit = rgetattr<(data) => Promise<void>>(settings, 'content.props.onSubmit', async (data) => { });
const _onCancel = rgetattr<(err) => Promise<void>>(settings, 'content.props.onCancel', async (err) => { });
const onSubmit = async (data) => {
close();
_onSubmit(data);
resolve(data);
}
const onCancel = async (data) => {
close();
_onCancel(data);
reject(data);
}
rsetattr(settings, 'content.props.onSubmit', onSubmit);
rsetattr(settings, 'content.props.onCancel', onCancel);
close = _this.openPopup(settings);
});
}
/**
* Helper Function to render a Toast.
* @param message The Message of the Toast
* @param type The Type.
* @param timeout A Timeout in [ms], after which the Toast should disapear. 0 = infinity
*/
public showToast(message: string, type?: 'info' | 'warning' | 'success' | 'error' | 'default' | 'dark' | 'light' | 'dark', autoClose = 5000) {
if (type !== undefined && type !== null) {
toast[type](message, {
position: "top-center",
autoClose,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
} else {
toast(message, {
position: "top-center",
autoClose,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
2020-10-26 07:39:34 +00:00
}
}
2020-10-29 18:20:42 +00:00
public get selectedTab(){
for (const tab of this.state.tabs.items) {
if (tab.id === this.state.tabs.active) {
return tab;
}
}
2020-10-26 07:39:34 +00:00
2020-10-29 18:20:42 +00:00
return null;
}
2020-10-25 20:14:51 +00:00
2020-10-29 18:20:42 +00:00
public async requestTab(){
if (typeof this.state.onNewTab === 'function') {
const tab = await this.state.onNewTab();
2020-10-25 20:14:51 +00:00
2020-10-29 18:20:42 +00:00
if (tab && typeof tab === 'object') {
this.state.tabs.items.push(tab);
2020-10-25 20:14:51 +00:00
2020-10-29 18:20:42 +00:00
this.selectTab(tab);
}
}
}
public async createTab(){
if (typeof this.state.onNewTab === 'function') {
const tab = await this.props.onNewTab();
if (tab && typeof tab === 'object') {
const tabs = this.tabs;
tabs.push(tab);
this.tabs = tabs;
await this.selectTab(tab);
}
}
}
public get tabs(): ITab[]{
return this.state.tabs?.items || [];
}
public set tabs(value: ITab[]){
this.setState({
tabs: {
active: this.activeTab,
items: value,
allowNewTabs: this.state.tabs.allowNewTabs
}
})
}
public get activeTab(): string{
return this.state.tabs.active;
}
public set activeTab(value: string){
this.setState({
tabs: {
active: value,
items: this.props.tabs.items,
allowNewTabs: this.props.tabs.allowNewTabs
}
})
}
/**
* Function to Select a Tab
* @param tab
*/
public async selectTab(tab: ITab | string) {
let newTabToDisplay: ITab;
if (typeof tab === "string"){
for (const _t of this.tabs){
if (_t.id = tab){
newTabToDisplay = _t;
break;
2020-10-25 20:14:51 +00:00
}
2020-10-29 18:20:42 +00:00
}
} else {
newTabToDisplay = tab;
}
2020-10-25 20:14:51 +00:00
2020-10-29 18:20:42 +00:00
if (typeof this.state.onTabSelect === 'function') {
const currentlySelected = this.selectedTab;
// Call the Async Function to select a Tab.
if (await this.state.onTabSelect(currentlySelected, newTabToDisplay)) {
this.activeTab = newTabToDisplay.id;
2020-10-25 20:14:51 +00:00
return true;
}
2020-10-29 18:20:42 +00:00
return false;
} else {
// Assign the Tab ID
this.activeTab = newTabToDisplay.id;
return true;
2020-10-25 20:14:51 +00:00
}
2020-10-29 18:20:42 +00:00
}
2020-10-25 20:14:51 +00:00
2020-10-29 18:20:42 +00:00
public async delteTab(tab: ITab | string, forced = false) {
2020-10-25 20:14:51 +00:00
2020-10-29 18:20:42 +00:00
let idx: number = -1;
let _tab: ITab = null;
const tabs = this.state.tabs.items;
for (const [_idx, _t] of tabs.entries()){
if (_t.id === (typeof tab === 'string' ? tab : tab.id)) {
idx = _idx;
_tab = _t;
break;
}
}
if (_tab != null) {
2020-10-25 20:14:51 +00:00
let removed = false;
2020-10-29 18:20:42 +00:00
if (typeof this.state.onTabDelete === 'function') {
2020-10-25 20:14:51 +00:00
// Call the Async Function to select a Tab.
2020-10-29 18:20:42 +00:00
if (await this.state.onTabDelete(_tab, forced)) {
2020-10-25 20:14:51 +00:00
removed = true;
}
} else {
removed = true;
}
if (removed) {
// Remove the Items.
2020-10-29 18:20:42 +00:00
tabs.splice(idx, 1);
2020-10-25 20:14:51 +00:00
// Check if the active element has been removed:
2020-10-29 18:20:42 +00:00
if (_tab.id == this.state.tabs.active) {
2020-10-25 20:14:51 +00:00
// Assign the ID of the Element.
2020-10-29 18:20:42 +00:00
const _idx = tabs.length > idx ? idx : tabs.length - 1;
if (_idx !== -1) {
2020-10-25 20:14:51 +00:00
// Assign the First ID.
2020-10-29 18:20:42 +00:00
const _newTab = tabs.length > _idx ? tabs[_idx] : null;
if (_newTab !== null) {
if (!await this.selectTab(_newTab) && typeof this.state.onNoTabSelected === 'function') {
2020-10-25 20:14:51 +00:00
// Send a Warning, that no Tabs has been selected
2020-10-29 18:20:42 +00:00
await this.state.onNoTabSelected();
2020-10-25 20:14:51 +00:00
}
2020-10-29 18:20:42 +00:00
} else if (typeof this.state.onNoTabSelected === 'function') {
2020-10-25 20:14:51 +00:00
// Send a Warning, that no Tabs has been selected
2020-10-29 18:20:42 +00:00
await this.state.onNoTabSelected();
2020-10-25 20:14:51 +00:00
}
2020-10-29 18:20:42 +00:00
} else if (typeof this.state.onNoTabSelected === 'function') {
2020-10-25 20:14:51 +00:00
// Send a Warning, that no Tabs has been selected
2020-10-29 18:20:42 +00:00
await this.state.onNoTabSelected();
2020-10-25 20:14:51 +00:00
}
}
2020-10-29 18:20:42 +00:00
this.tabs = tabs;
2020-10-25 20:14:51 +00:00
}
}
2020-10-29 18:20:42 +00:00
}
2020-10-25 20:14:51 +00:00
2020-10-29 18:20:42 +00:00
public render() {
2020-09-02 15:00:03 +00:00
return (<>
2020-10-29 18:20:42 +00:00
<Toolbar toolbar={this.state.toolbar} generateData={this.state.generateData} brand={{
label: 'nopeBackend',
ref: '/',
type: 'link'
}}></Toolbar>
2020-09-02 15:00:03 +00:00
<Container fluid style={mainStyle}>
<Row style={mainStyle}>
2020-10-29 21:20:47 +00:00
<Col className="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse" style={{paddingTop: '10px', paddingLeft: '10px', paddingRight: '10px'}}>
<div style={{ overflow: 'scroll', height: '90vh'}}>
<Selection selection={this.state.selection} allowUserSelect={this.state.allowUserSelect} onItemSelected={this.state.onItemSelected}></Selection>
</div>
2020-09-02 15:00:03 +00:00
</Col>
2020-10-29 18:20:42 +00:00
<Col role="main" style={contentStyle} ref={this._mainRef}>
{this.state.tabs ?
<Nav variant="tabs" activeKey={this.state.tabs.active}>
{this.state.tabs.items.map((tab, idx) => {
if (tab.delteable && this.state.tabs.allowNewTabs) {
2020-09-07 05:21:53 +00:00
return (
<Nav.Item key={tab.id}>
<Nav.Link onSelect={_ => {
2020-10-29 18:20:42 +00:00
this.selectTab(tab);
2020-10-26 08:54:30 +00:00
}} eventKey={tab.id}>{tab.label + ' '}
2020-09-07 05:21:53 +00:00
<FaTimesCircle onClick={
2020-10-25 20:14:51 +00:00
(e) => {
2020-09-07 11:05:40 +00:00
e.preventDefault();
e.stopPropagation();
2020-10-25 20:14:51 +00:00
2020-10-29 18:20:42 +00:00
this.delteTab(tab);
2020-09-07 05:21:53 +00:00
}
} ></FaTimesCircle>
</Nav.Link>
</Nav.Item>
);
} else {
return (
<Nav.Item key={tab.id}>
<Nav.Link onSelect={_ => {
2020-10-29 18:20:42 +00:00
this.selectTab(tab);
2020-09-07 05:21:53 +00:00
}} eventKey={tab.id}>{tab.label}</Nav.Link>
</Nav.Item>
);
}
})}
2020-09-07 11:05:40 +00:00
2020-10-29 18:20:42 +00:00
{this.state.tabs.allowNewTabs ?
2020-09-07 11:05:40 +00:00
<Nav.Item>
2020-10-29 18:20:42 +00:00
<Nav.Link eventKey='_NEW_ITEM' onSelect={_ => this.requestTab()}>
2020-09-07 11:05:40 +00:00
+
</Nav.Link>
</Nav.Item> : ''
}
2020-09-07 05:21:53 +00:00
</Nav>
: ''
}
2020-09-02 15:00:03 +00:00
{/* style={{height: '100%'}} */}
<div ref={this._ref}></div>
</Col>
2020-10-29 21:20:47 +00:00
<Col className="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse" style={{paddingTop: '10px', paddingLeft: '10px', paddingRight: '10px'}}>
<div style={{ overflow: 'scroll', height: '90vh'}}>
<Selection selection={this.state.selection} allowUserSelect={this.state.allowUserSelect} onItemSelected={this.state.onItemSelected}></Selection>
</div>
</Col>
2020-09-02 15:00:03 +00:00
</Row>
</Container>
2020-10-26 07:39:34 +00:00
{/* Render a Modal dynamically. */}
2020-10-29 18:20:42 +00:00
{this.state.modalSettings != undefined ?
<Modal
show={this.state.modalVisible}
backdrop={typeof this.state.modalSettings.backdrop !== undefined ? this.state.modalSettings.backdrop : 'static'}
2020-10-26 07:39:34 +00:00
keyboard={false}
2020-10-29 18:20:42 +00:00
size={this.state.modalSettings.size}
centered={this.state.modalSettings.centered || false}
onHide={this.state.modalSettings.onHide}
scrollable
2020-10-26 07:39:34 +00:00
>
2020-10-29 18:20:42 +00:00
{
this.state.modalSettings.header ?
<Modal.Header>
<Modal.Title>{this.state.modalSettings.header}</Modal.Title>
</Modal.Header> :
''
}
2020-10-26 07:39:34 +00:00
<Modal.Body>
2020-10-29 18:20:42 +00:00
{/* Render the dynamic Component */}
<DynamicRenderer component={this.state.modalSettings.content.component} props={this.state.modalSettings.content.props} />
2020-10-26 07:39:34 +00:00
</Modal.Body>
</Modal>
2020-10-29 18:20:42 +00:00
: ''}
{/* Container for the Toasts */}
<ToastContainer
position="top-center"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
2020-09-02 15:00:03 +00:00
</>);
}
2020-09-02 05:49:53 +00:00
}
2020-09-02 15:00:03 +00:00
export default Layout;