From 2e2179b8b212756ae90f99fb9c95e6a1ef0ccb19 Mon Sep 17 00:00:00 2001 From: rbokhorst <rbokhorst@astron.nl> Date: Wed, 26 Dec 2018 10:36:04 +0000 Subject: [PATCH] OSB-35: fix table height and re-render --- .gitattributes | 1 + .../src/components/FillHeight.js | 88 +++++++++++++++++ .../src/components/StationTestView.js | 95 ++++++++++++------- .../src/components/Toolbar.js | 1 + .../src/pages/StationOverviewPage.js | 91 +++++++++++------- .../maintenancedb_view/src/pages/TilesPage.js | 8 +- .../src/themes/lofar-styles.css | 4 + .../src/themes/lofar-styles.scss | 6 ++ .../maintenancedb_view/src/themes/lofar.css | 4 + 9 files changed, 230 insertions(+), 68 deletions(-) create mode 100644 LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js diff --git a/.gitattributes b/.gitattributes index c18094c8f08..37dd22f583a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1850,6 +1850,7 @@ LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.test.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/api_configuration.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaErrorDetails.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaView.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.css -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.scss -text diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js new file mode 100644 index 00000000000..f36a94934af --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js @@ -0,0 +1,88 @@ +import React from "react"; + +/* + * FillHeight: Creates a div that fills the remaining height of the viewport. + * Window resize is monitored to scale the div accordingly. + * + * This component uses the 'render prop' pattern where the 'children' prop is used + * as the render function. The height of the div (in an object) is passed to + * the function for use by subcomponents. E.g.: + * + * <FillHeight> + * { (props) => <MyComponent hgt={props.height} /> } + * </FillHeight> + */ +class FillHeight extends React.Component { + + static defaultProps = { + className: 'fill-height-container', // classname for the container div + gutterBottom: 10, // px, leave some space at the bottom + minHeight: 200 // px, minimum height + }; + + state = { + height: 500 // px, height of the container div + }; + + node = null; // internal ref to the DOM node (div) + mounted = false; // bool + resizeTimeout = null; // throttling of render during resize + + + componentDidMount() { + this.mounted = true; + + window.addEventListener("resize", () => { + window.clearTimeout(this.resizeTimeout); + this.resizeTimeout = window.setTimeout(this.onWindowResize, 200); + }); + + // force a render now that it has been mounted + // (the offset is known at this point) + this.onWindowResize(); + } + + componentWillUnmount() { + this.mounted = false; + window.removeEventListener("resize", this.onWindowResize); + } + + onWindowResize = () => { + if (!this.mounted || !this.node) { + return; + } + + let offset = this.node.getBoundingClientRect().top; + let height = window.innerHeight - offset - this.props.gutterBottom; + if (height < this.props.minHeight) { + height = this.props.minHeight; + } + + this.setState({ + height + }); + }; + + getNodeRef = (node) => { + this.node = node; + } + + render() { + // Create a simple div for measuring the viewport offset when it mounts, + // then re-render. + if (!this.mounted) { + return ( + <div ref={this.getNodeRef} /> + ); + } + + // Render prop pattern; pass height to child render function + return ( + <div ref={this.getNodeRef} className={this.props.className} style={{height: this.state.height+'px'}}> + { this.props.children({height: this.state.height}) } + </div> + ); + } +}; + +export default FillHeight; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js index 8c3746d77c2..8dee19a22df 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js @@ -21,6 +21,8 @@ import ReactTableContainer from "react-table-container"; import moment from 'moment'; import {withRouter} from "react-router"; import classNames from "classnames"; +import FillHeight from '../components/FillHeight.js'; + // CSS import '../themes/lofar-styles.css'; @@ -243,6 +245,7 @@ class RTSMLines extends Component { this.setState({ displaySingleTests: !this.state.displaySingleTests }); + this.props.callback(); }; render() { @@ -310,7 +313,8 @@ class ComponentClass extends Component { component_type={this.props.type} station_name={this.props.station_name} station_type={this.props.station_type} - data={data}/>) + data={data} + callback={this.rerender} />) } renderTestLines(data, component_ids) { @@ -340,11 +344,17 @@ class ComponentClass extends Component { return rows } + rerender = () => { + this.setState({ state: this.state }); + } + render() { const comp_ids = this.computeComponentIDList(this.props.type); + //<ReactTableContainer width="100%" height="79vh"> + this.i++; return ( - <ReactTableContainer width="100%" height="79vh"> + <ReactTableContainer width="100%" height={this.props.height+'px'} id={this.i}> <table className="stv-table table-sm table-hover table-bordered"> <thead className="stv-tableheader"> <tr> @@ -365,6 +375,21 @@ class ComponentClass extends Component { } } +/* + * Render a Tab item + */ +function Tab({label, onClick, isActive}) { + const cls = isActive ? 'clickable-tab-active' : 'clickable-tab-unactive'; + + return ( + <NavItem className="clickable-tab"> + <NavLink className={cls} onClick={onClick}> + {label} + </NavLink> + </NavItem> + ); +} + /** * StationTestView class. */ @@ -374,15 +399,6 @@ class StationTestViewC extends Component { activeTab: undefined } - toggleTab = (e) => { - let tab = e.currentTarget.innerHTML; - if (this.state.activeTab !== tab) { - this.setState({ - activeTab: tab - }); - } - } - // Set the activeTab to the first component if it wasn't set yet or when // the station changed and doesn't have the active component static getDerivedStateFromProps(props, state) { @@ -401,43 +417,54 @@ class StationTestViewC extends Component { return null; } - tabClass = (componentType) => { - const className = (this.state.activeTab === componentType ? 'active' : 'unactive'); - return 'clickable-tab-'+className - } - // Do not (re)render when data is loading (performance improvement) shouldComponentUpdate(nextProps, nextState, nextContext) { return nextProps.isLoading ? false : true; } + toggleTab = (e) => { + let tab = e.currentTarget.innerHTML; + if (this.state.activeTab !== tab) { + this.setState({ + activeTab: tab + }); + } + } + render() { const stationType = LOFARDefinitions.stationTypeFromName(this.props.selectedStation); const componentTypes = Object.keys(this.props.data).sort(); + if (this.props.isLoading) { + return null; + } + return ( <div> <Nav tabs className="component-type-selector"> - { componentTypes.map((componentType, key) => - <NavItem key={key} className="clickable-tab"> - <NavLink className={this.tabClass(componentType)} - onClick={this.toggleTab}> - {componentType} - </NavLink> - </NavItem>) + { + componentTypes.map((componentType, key) => + <Tab key={key} onClick={this.toggleTab} isActive={this.state.activeTab===componentType} label={componentType} /> + ) } </Nav> - <TabContent activeTab={this.state.activeTab}> - { componentTypes.map((componentType, key) => - <TabPane key={key} tabId={componentType}> - <ComponentClass key={componentType} - station_type={stationType} - type={componentType} - station_name={this.props.selectedStation} - data={this.props.data[componentType]} /> - </TabPane>) - } - </TabContent> + <FillHeight className="border-right"> + { ({height}) => ( + <TabContent activeTab={this.state.activeTab}> + {componentTypes.map((componentType, key) => + <TabPane key={key} tabId={componentType}> + <ComponentClass key={componentType} + station_type={stationType} + type={componentType} + station_name={this.props.selectedStation} + data={this.props.data[componentType]} + height={height} + /> + </TabPane> + )} + </TabContent> + )} + </FillHeight> </div>); } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js index bd52ef91c6a..8845afbc9f0 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js @@ -21,6 +21,7 @@ import MultiSelectDropdown from '../components/MultiSelectDropdown.js' import {AntennaIdsPerTypeStationType} from '../utils/LOFARDefinitions.js' // CSS import './Toolbar.css' +import 'react-datepicker/dist/react-datepicker.css'; /** diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js index 10863e66da0..40df6c6b928 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js @@ -9,47 +9,81 @@ import StationTestView from '../components/StationTestView'; import StationTestChildView from '../components/StationTestChildView'; import { composeQueryString } from '../utils/utils.js'; import { Toolbar, DateRangeSelector, TestTypeSelector, ErrorTypesSelector } from '../components/Toolbar' +import FillHeight from '../components/FillHeight.js'; -// CSS -import 'react-datepicker/dist/react-datepicker.css'; +/* + * Display an Alert + */ +function ErrorAlert({doDisplay, message}){ + + if (! doDisplay) { + return null; + } + + // The 10px is the margin that ResponsiveGridLayout uses + return ( + <Alert style={{margin: '10px'}} color="warning"> + {message} + </Alert> + ); +} + +/* + * Display the page body (below the header) + */ +function PageBody({doDisplay, url}) { + + if (! doDisplay) { + return null; + } + + return ( + <Container fluid={true}> + <Row> + <Col md="8" className="col-padding"> + <StationTestView url={url} /> + </Col> + <Col md="4" className="col-padding"> + <StationTestChildView /> + </Col> + </Row> + </Container> + ); +} class StationOverviewPageC extends Component { - getStationSummaryURL(){ - const parameters = {}; + getStationSummaryURL() { if (this.isParameterMissing()) { - return ""; + return ''; } - parameters.station_name = this.props.selectedStation; - parameters.test_type = this.props.testType; - parameters.from_date = moment(this.props.startDate).format('YYYY-MM-DD'); - parameters.to_date = moment(this.props.endDate).format('YYYY-MM-DD'); - parameters.error_types = this.props.selectedErrorTypes - const baseURL = '/api/view/ctrl_station_component_errors?format=json'; + const parameters = { + format: 'json', + station_name: this.props.selectedStation, + test_type: this.props.testType, + from_date: moment(this.props.startDate).format('YYYY-MM-DD'), + to_date: moment(this.props.endDate).format('YYYY-MM-DD'), + error_types: this.props.selectedErrorTypes + }; + + const baseURL = '/api/view/ctrl_station_component_errors'; const queryString = composeQueryString(parameters); - return `${baseURL}&${queryString}` + + return `${baseURL}?${queryString}` } isParameterMissing(){ const stationName = this.props.selectedStation; - return stationName === undefined || - stationName === "" - } - - renderErrorIfParameterMissing(){ - if (this.isParameterMissing()){ - // The 10px is the margin that ResponsiveGridLayout uses - return <Alert style={{margin: '10px'}} color="warning">Please select a station</Alert> - }else { - return "" - } + return stationName === undefined || stationName === ""; } render() { + const parmMissing = this.isParameterMissing(); + return ( <React.Fragment> <Header active_page={this.props.location} /> @@ -59,15 +93,8 @@ class StationOverviewPageC extends Component { <DateRangeSelector /> <ErrorTypesSelector /> </Toolbar> - {this.renderErrorIfParameterMissing()} - { this.isParameterMissing() ? "" : - <Container fluid={true} style={{padding: '10px'}}> - <Row> - <Col md="8"><StationTestView url={this.getStationSummaryURL()}/></Col> - <Col md="4"><StationTestChildView /></Col> - </Row> - </Container> - } + <ErrorAlert doDisplay={parmMissing} message="Please select a station" /> + <PageBody doDisplay={!parmMissing} url={this.getStationSummaryURL()} /> </React.Fragment> ); } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js index a25e826d39c..4aa496d0baa 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js @@ -61,8 +61,12 @@ class TilesPageC extends Component { { parmMissing ? null : <Container fluid={true} style={{padding: '10px'}}> <Row> - <Col md="8"><AntennaView url={url}/></Col> - <Col md="4"><AntennaErrorDetails/></Col> + <Col md="8" className="col-padding"> + <AntennaView url={url}/> + </Col> + <Col md="4" className="col-padding"> + <AntennaErrorDetails/> + </Col> </Row> </Container> } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css index 7bc0991b0af..3a0c2bded0c 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css @@ -89,3 +89,7 @@ img { .form-input button:hover { background-color: #e8e8ec !important; } + +/* Padding on column in bootstrap grid */ +.row .col-padding { + padding-top: 15px; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss index 76cbcace81d..a06cfc16b5f 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss @@ -106,3 +106,9 @@ $griditem-color: $secondary-dark; .form-input button:hover { background-color: $secondary-light !important; } + + +/* Padding on column in bootstrap grid */ +.row .col-padding { + padding-top: 15px; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css index 8ef4aa74ceb..aab891291b8 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css @@ -6408,3 +6408,7 @@ img { .form-input button:hover { background-color: #e8e8ec !important; } + +/* Padding on column in bootstrap grid */ +.row .col-padding { + padding-top: 15px; } -- GitLab