diff --git a/package.json b/package.json index 0e37065b12..d0adf45e65 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "react-color": "2.11.3", "react-confirm-button": "0.0.2", "react-copy-to-clipboard": "4.1.0", + "react-data-grid": "2.0.41", "react-dnd": "2.2.3", "react-dnd-html5-backend": "2.2.3", "react-dock": "0.2.3", diff --git a/web/client/components/buttons/GlobeViewSwitcherButton.jsx b/web/client/components/buttons/GlobeViewSwitcherButton.jsx index 4b0c6f5a4e..64e0164cd1 100644 --- a/web/client/components/buttons/GlobeViewSwitcherButton.jsx +++ b/web/client/components/buttons/GlobeViewSwitcherButton.jsx @@ -82,7 +82,7 @@ const GlobeViewSwitcherButton = React.createClass({ ].reduce((result, key) => { result[key] = this.props[key]; return result; }, {}); }, render() { - return } />; + return } />; } }); diff --git a/web/client/components/data/featuregrid/FeatureGrid.jsx b/web/client/components/data/featuregrid/FeatureGrid.jsx index f4f688b249..941f729e8e 100644 --- a/web/client/components/data/featuregrid/FeatureGrid.jsx +++ b/web/client/components/data/featuregrid/FeatureGrid.jsx @@ -188,7 +188,7 @@ const FeatureGrid = React.createClass({ flexDirection: "column", height: "100%" }}> -
+
id value of the dockable panel + * @prop {string} dimMode. If none - content is not dimmed, if transparent - pointer events are disabled (so you can click through it), if opaque - click on dim area closes the dock. Default is none + * @prop {number} dockSize. Size of dock panel (width or height, depending on position). Is a % value [0~1] + * @prop {bool} isVisible. If true, dock is visible. Default is true. + * @prop {number} maxDockSize. The maximum extension in %. Default 1.0 + * @prop {number} minDockSize. The minimum extension in %. Default 0.1 + * @prop {string} position. Side to dock (left, right, top or bottom). Default is bottom. + * @prop {bool} fluid. If true, resize dock proportionally on window resize. Default is true. + * @prop {function} setDockSize. The metod called when the dockable panel is resized + * @prop {object} toolbar. it contains the toolbar + * @prop {object} toolbarHeight. the height of the toolbar in px. Default 40 + * @prop {object} wrappedComponent. A connected Component to be rendered inside the dock panel + * @prop {number} zIndex. Positioned below dialogs, above left menu + * + */ +const DockablePanel = React.createClass({ + propTypes: { + id: React.PropTypes.string, + dimMode: React.PropTypes.string, + dockSize: React.PropTypes.number, + isVisible: React.PropTypes.bool, + fluid: React.PropTypes.bool, + maxDockSize: React.PropTypes.number, + minDockSize: React.PropTypes.number, + position: React.PropTypes.string, + setDockSize: React.PropTypes.func, + toolbar: React.PropTypes.object, + toolbarHeight: React.PropTypes.number, + wrappedComponent: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.func]), + zIndex: React.PropTypes.number + }, + contextTypes: { + messages: React.PropTypes.object + }, + getDefaultProps() { + return { + dimMode: "none", + dockSize: 0.35, + fluid: true, + isVisible: true, + maxDockSize: 1.0, + minDockSize: 0.1, + position: "bottom", + setDockSize: () => {}, + toolbar: null, + toolbarHeight: 40, + wrappedComponent: {}, + zIndex: 1030 + }; + }, + getHeight(pos) { + return pos === "top" || pos === "bottom" ? true : undefined; + }, + getWidth(pos) { + return pos === "left" || pos === "right" ? true : undefined; + }, + render() { + const WrappedComponent = this.props.wrappedComponent; + return ( + +
+ {this.props.wrappedComponent !== null ? () : null } +
+ {this.props.toolbar} +
+ ); + }, + limitDockHeight(size) { + if (size >= this.props.maxDockSize) { + this.props.setDockSize(this.props.maxDockSize); + } else if (size <= this.props.minDockSize) { + this.props.setDockSize(this.props.minDockSize); + } else { + this.props.setDockSize(size); + } + } +}); + +module.exports = DockablePanel; diff --git a/web/client/components/misc/ResizableGrid.jsx b/web/client/components/misc/ResizableGrid.jsx new file mode 100644 index 0000000000..5731a5b24e --- /dev/null +++ b/web/client/components/misc/ResizableGrid.jsx @@ -0,0 +1,101 @@ +/* + * Copyright 2017, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +const React = require('react'); +const ReactDataGrid = require('react-data-grid'); +/** + * Component for rendering a feature grid. + * @memberof components.ResizableGrid + * @class + * @prop {object[]} columns. The columns rendered in the header. Each object is composed by key,name,[reizable=true|false]. + * @prop {number} headerRowHeight the height in pixels of the rows in the header. Default 55 + * @prop {number} minHeight the min height of the grid container. Default 250 + * @prop {number} minWidth the min width of the grid container. + * @prop {string} refGrid the reference to the react-data-grid-component + * @prop {number} rowHeight the height of the rows in the grid. Default 30 + * @prop {string} rowKey the key used to distinguish rows. + * @prop {object} rowSelection The object used to handle selection of rows. It puts a column of check as the first row. + * @prop {object[]} rows. The features passed to the grid. + * @prop {number} size. The size of the dock panel wrapping this component. + * + */ +const ResizableGrid = React.createClass({ + propTypes: { + columns: React.PropTypes.array.isRequired, + headerRowHeight: React.PropTypes.number, + minHeight: React.PropTypes.number.isRequired, + minWidth: React.PropTypes.number, + refGrid: React.PropTypes.string, + rowHeight: React.PropTypes.number.isRequired, + rowKey: React.PropTypes.string, + rowSelection: React.PropTypes.object, + rows: React.PropTypes.array.isRequired, + size: React.PropTypes.object + }, + contextTypes: { + messages: React.PropTypes.object + }, + getDefaultProps() { + return { + columns: [], + headerRowHeight: 55, + minHeight: 250, + minWidth: null, + refGrid: "grid", + rowHeight: 30, + rowKey: "id", + rowSelection: null, + rows: [] + }; + }, + getInitialState() { + return { + minHeight: this.props.minHeight, + minWidth: this.props.minWidth + }; + }, + componentWillReceiveProps(newProps) { + if (this.props.size.width !== newProps.size.width ) { + this.setState({ + minWidth: this.getWidth(this.refs.grid), + minHeight: this.getHeight(this.refs.grid)} + ); + } + if (this.props.size.height !== newProps.size.height ) { + this.setState({ + minHeight: this.getHeight(this.refs.grid)} + ); + } + }, + getHeight(element) { + return element && element.getDataGridDOMNode().clientHeight || this.props.minHeight; + }, + getWidth(element) { + return element && element.getDataGridDOMNode().clientWidth || this.props.minWidth; + }, + render() { + return ( + + ); + }, + rowGetter(i) { + return this.props.rows[i]; + } +}); + +module.exports = ResizableGrid; diff --git a/web/client/components/misc/__tests__/DockablePanel-test.jsx b/web/client/components/misc/__tests__/DockablePanel-test.jsx new file mode 100644 index 0000000000..7e32dd3883 --- /dev/null +++ b/web/client/components/misc/__tests__/DockablePanel-test.jsx @@ -0,0 +1,41 @@ +/* + * Copyright 2017, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +const React = require("react"); +const expect = require('expect'); +const ReactDOM = require('react-dom'); +const DockablePanel = require('../DockablePanel'); + +const defaultProps = { + dockSize: 0.35, + wrappedComponent: null, + position: "bottom", + maxDockSize: 1.0, + minDockSize: 0.1, + setDockSize: () => {} +}; +describe("Test DockablePanel Component", () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + + it('Test DockablePanel rendering without tools', () => { + let comp = ReactDOM.render( + , document.getElementById("container")); + expect(comp).toExist(); + + }); + + +}); diff --git a/web/client/components/misc/__tests__/ResizableGrid-test.jsx b/web/client/components/misc/__tests__/ResizableGrid-test.jsx new file mode 100644 index 0000000000..16d71145ca --- /dev/null +++ b/web/client/components/misc/__tests__/ResizableGrid-test.jsx @@ -0,0 +1,148 @@ +/* + * Copyright 2017, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +const React = require("react"); +const expect = require('expect'); +const ReactDOM = require('react-dom'); +const ResizableGrid = require('../ResizableGrid'); + +const state = { + columns: [ + { + key: 'id', + name: 'id', + resizable: true + }, { + key: 'name', + name: 'nome livello', + resizable: true + }], + features: [{ + id: "1", + name: "Edifici" + }, { + id: "2", + name: "Aiuola" + }, { + id: "3", + name: "Edifici" + }, { + id: "4", + name: "Aiuola" + }, { + id: "5", + name: "Edifici" + }, { + id: "6", + name: "Aiuola" + }, { + id: "7", + name: "Edifici" + }, { + id: "8", + name: "Aiuola" + }, { + id: "9", + name: "Edifici" + }, { + id: "10", + name: "Aiuola" + }, { + id: "11", + name: "Edifici" + }, { + id: "12", + name: "Aiuola" + }, { + id: "13", + name: "Edifici" + }, { + id: "14", + name: "Aiuola" + }, { + id: "15", + name: "extra 15" + }, { + id: "16", + name: "extra 16" + }], + open: true, + selectBy: { + keys: { + rowKey: '', + values: [] + } + } + }; + +const defaultProps = { + rowsCount: state.features.length, + minHeight: 250, + minWidth: 600, + size: { + width: true, + height: false, + size: 0.35 + }, + position: "bottom", + columns: state.columns, + selectBy: state.selectBy, + rows: state.features +}; +describe("Test ResizableGrid Component", () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + + it('Test ResizableGrid rendering without tools', () => { + let comp = ReactDOM.render( + , document.getElementById("container")); + expect(comp).toExist(); + }); + + it('Test ResizableGrid re-rendering for entering in the componentWillReceiveProps', () => { + let comp = ReactDOM.render( + , document.getElementById("container")); + expect(comp).toExist(); + let comp2 = ReactDOM.render( + , document.getElementById("container")); + expect(comp2).toExist(); + + let comp3 = ReactDOM.render( + , document.getElementById("container")); + expect(comp3).toExist(); + + }); + +}); diff --git a/web/client/themes/default/ms2-theme.less b/web/client/themes/default/ms2-theme.less index 8735028f1f..9bc237e952 100644 --- a/web/client/themes/default/ms2-theme.less +++ b/web/client/themes/default/ms2-theme.less @@ -952,7 +952,57 @@ select.form-control option { background-color: @ms2-color-primary !important; color: @ms2-color-text-primary !important; } -/* feauture grid */ +/* feature grid made by react-data-grid */ +.dockpanel-wrapped-component .react-grid-Header{ + background: @ms2-color-primary; + color: @ms2-color-text-primary; + font-family: @font-family-base; +} +.dockpanel-wrapped-component .react-grid-Main .react-grid-Header .react-grid-HeaderCell{ + background: @ms2-color-primary; + color: @ms2-color-text-primary; + font-family: @font-family-base; + height:55px !important; + text-align: center; + padding-top: 17px; +} +.dockpanel-wrapped-component .react-grid-Row--even .react-grid-Cell { background-color: #f2f2f2; } + +.dockpanel-wrapped-component .react-grid-Main, .react-grid-Container { + height: 100%; + width: 100% !important; +} +.dockpanel-wrapped-component .react-grid-Container { + width: 100% !important; +} +// dock panel +#dock-toolbar #left-tools.left-tools{ + position: absolute; + left: 10px; + bottom: 2px; +} +#dock-toolbar #right-tools.right-tools{ + position: absolute; + right: 2px; + bottom: 2px; +} +.dockpanel-wrapped-component{ + display: flex; + flex: 1 0 auto; + flexDirection: column; + width: 100%; +} +.dockpanel-wrapped-component .react-grid-Grid{ + min-height: 100% !important; +} + + +.radio-custom+.radio-custom-label:before, .react-grid-checkbox+.react-grid-checkbox-label:before{ + width: 15px; + height: 15px; +} + +// feature grid .ag-fresh .ag-header, .ag-fresh .ag-tool-panel-container { background: @ms2-color-primary !important; @@ -960,7 +1010,7 @@ select.form-control option { font-family: @font-family-base; } -.ag-fresh .ag-tool-panel-container .ag-list-selection { + .ag-fresh .ag-tool-panel-container .ag-list-selection { color: #000000; } diff --git a/web/client/translations/data.de-DE b/web/client/translations/data.de-DE index ea8a0086ff..8ac53df258 100644 --- a/web/client/translations/data.de-DE +++ b/web/client/translations/data.de-DE @@ -46,6 +46,10 @@ "featureTypeError": "Layerattribute können nicht geladen werden", "elevation": "Höhe" }, + "dock": { + "row": "{rowsSelected} reihe ausgewählt", + "rows": "{rowsSelected} reihen ausgewählt" + }, "globeswitcher": { "tooltipDeactivate": "Beenden Sie den 3D-Modus", "tooltipActivate": "Geben Sie den 3D-Modus ein" diff --git a/web/client/translations/data.en-US b/web/client/translations/data.en-US index fae566375f..1b576e56d8 100644 --- a/web/client/translations/data.en-US +++ b/web/client/translations/data.en-US @@ -46,6 +46,10 @@ "featureTypeError": "Cannot load layer attributes", "elevation": "Elevation" }, + "dock": { + "row": "{rowsSelected} row selected", + "rows": "{rowsSelected} rows selected" + }, "globeswitcher": { "tooltipDeactivate": "Exit 3D Mode", "tooltipActivate": "Enter 3D Mode" diff --git a/web/client/translations/data.fr-FR b/web/client/translations/data.fr-FR index d6a3a478a3..e4eee2a39b 100644 --- a/web/client/translations/data.fr-FR +++ b/web/client/translations/data.fr-FR @@ -46,6 +46,10 @@ "featureTypeError": "Impossible de charger les attributs de calque", "elevation": "élévation" }, + "dock": { + "row": "{rowsSelected} ligne sélectionné", + "rows": "{rowsSelected} lignes sélectionné" + }, "globeswitcher": { "tooltipDeactivate": "Sortie en mode 3D", "tooltipActivate": "Entrez le mode 3D" diff --git a/web/client/translations/data.it-IT b/web/client/translations/data.it-IT index 57f5414aca..ad22586c39 100644 --- a/web/client/translations/data.it-IT +++ b/web/client/translations/data.it-IT @@ -46,6 +46,10 @@ "featureTypeError": "Impossibile caricare gli attributi del layer", "elevation": "Elevazione" }, + "dock": { + "row": "{rowsSelected} riga selezionata", + "rows": "{rowsSelected} righe selezionate" + }, "globeswitcher": { "tooltipDeactivate": "Disattiva la modalità 3D", "tooltipActivate": "Attiva la modalità 3D"